diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..94b022cd204 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +### OrientDB Version: +### Java Version: +### OS: + +## Expected behavior + + +## Actual behavior + + +## Steps to reproduce + + diff --git a/.gitignore b/.gitignore index 73711f377ee..5c3be6832af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,220 @@ +build.number +*.class +*/target/* +target +*/test-output +out +*/databases +*.gz +*.patch + +# Package Files # +*.war +*.ear + +# Eclipse IDE files +*.classpath +*.project +.settings/ +*.prefs + +#IntelliJ IDEA files +*.iml +.idea/ + +tests/src/test/java/com/orientechnologies/orient/test/database/auto/_*.xml + +export/ + +*/.DS_Store + +distribution/.orientdb_history +### Gradle template +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties +### macOS template +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### Maven template target/ pom.xml.tag pom.xml.releaseBackup +pom.xml.versionsBackup pom.xml.next release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties -*.iml -.idea +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar +### Eclipse template + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.loadpath +.recommenders + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/.travis.yml b/.travis.yml index b80335dd32d..cbf418036d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ sudo: false language: java +cache: + directories: + - $HOME/.m2 +install: mvn clean install -Dmaven.test.failure.ignore=true branches: only: - develop jdk: - oraclejdk8 -before_install: - - sed -i.bak -e 's|https://nexus.codehaus.org/snapshots/|https://oss.sonatype.org/content/repositories/codehaus-snapshots/|g' ~/.m2/settings.xml diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..a1ca9ba782f --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,78 @@ +#!groovy +node("master") { + milestone() + lock(resource: "${env.BRANCH_NAME}", inversePrecedence: true) { + ansiColor('xterm') { + milestone() + def mvnHome = tool 'mvn' + def mvnJdk8Image = "orientdb/mvn-gradle-zulu-jdk-8" + def mvnJdk7Image = "orientdb/mvn-gradle-zulu-jdk-7" + + stage('Source checkout') { + checkout scm + } + + try { +// stage('Compile on Java7') { +// docker.image("${mvnJdk7Image}") +// .inside("${env.VOLUMES}") { +// sh "${mvnHome}/bin/mvn --batch-mode -V -U clean compile -Dmaven.test.failure.ignore=true -Dsurefire.useFile=false" +// } +// } + + stage('Run tests on Java7') { + docker.image("${mvnJdk7Image}") + .inside("${env.VOLUMES}") { + try { + //skip integration test for now + sh "${mvnHome}/bin/mvn -V -fae clean install -Dsurefire.useFile=false -DskipITs" + sh "${mvnHome}/bin/mvn -f distribution/pom.xml clean" + sh "${mvnHome}/bin/mvn --batch-mode -V deploy -DskipTests -DskipITs" + } finally { + junit allowEmptyResults: true, testResults: '**/target/surefire-reports/TEST-*.xml' + + } + } + } + +// stage('Run QA/Integration tests on Java8') { +// docker.image("${mvnJdk8Image}") +// .inside("${env.VOLUMES}") { +// try { +// sh "${mvnHome}/bin/mvn -f distribution/pom.xml clean install -Pqa" +// } finally { +// junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml' +// +// } +// } +// } + + stage('Publish Javadoc') { + docker.image("${mvnJdk8Image}") + .inside("${env.VOLUMES}") { + sh "${mvnHome}/bin/mvn javadoc:aggregate" + sh "rsync -ra --stats ${WORKSPACE}/target/site/apidocs/ -e ${env.RSYNC_JAVADOC}/${env.BRANCH_NAME}/" + } + } + + stage("Downstream projects") { + build job: "orientdb-spatial-multibranch/${env.BRANCH_NAME}", wait: false + //excluded: too long + //build job: "orientdb-enterprise-multibranch/${env.BRANCH_NAME}", wait: false + build job: "orientdb-security-multibranch/${env.BRANCH_NAME}", wait: false + build job: "orientdb-neo4j-importer-multibranch/${env.BRANCH_NAME}", wait: false + build job: "orientdb-teleporter-multibranch/${env.BRANCH_NAME}", wait: false + build job: "spring-data-orientdb-multibranch/${env.BRANCH_NAME}", wait: false + } + + slackSend(color: '#00FF00', message: "SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + + } catch (e) { + currentBuild.result = 'FAILURE' + slackSend(channel: '#jenkins-failures', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + throw e; + } + } + + } +} diff --git a/Jenkinsfile-crash.groovy b/Jenkinsfile-crash.groovy new file mode 100644 index 00000000000..2ddb47c4188 --- /dev/null +++ b/Jenkinsfile-crash.groovy @@ -0,0 +1,35 @@ +#!groovy +node("master") { + def mvnHome = tool 'mvn' + def mvnJdk8Image = "orientdb/mvn-gradle-zulu-jdk-8" + + stage('Source checkout') { + + checkout scm + } + + stage('Run crash tests on java8') { + + try { + timeout(time: 240, unit: 'MINUTES') { + docker.image("${mvnJdk8Image}") + .inside("${env.VOLUMES}") { + sh "${mvnHome}/bin/mvn -f ./server/pom.xml --batch-mode -V -U -e -Dmaven.test.failure.ignore=true clean test-compile failsafe:integration-test -Dsurefire.useFile=false" + } + } + + if (currentBuild.previousBuild == null || currentBuild.previousBuild.result != currentBuild.result) { + slackSend(color: '#00FF00', message: "SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + + } catch (e) { + currentBuild.result = 'FAILURE' + if (currentBuild.previousBuild == null || currentBuild.previousBuild.result != currentBuild.result) { + slackSend(color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + throw e; + } + } + +} + diff --git a/Jenkinsfile-distributed.groovy b/Jenkinsfile-distributed.groovy new file mode 100644 index 00000000000..b6bcf595c7e --- /dev/null +++ b/Jenkinsfile-distributed.groovy @@ -0,0 +1,35 @@ +#!groovy +node("master") { + def mvnHome = tool 'mvn' + def mvnJdk8Image = "orientdb/mvn-gradle-zulu-jdk-8" + + stage('Source checkout') { + + checkout scm + } + + stage('Run distributed test on Java8') { + + try { + timeout(time: 180, unit: 'MINUTES') { + docker.image("${mvnJdk8Image}") + .inside("${env.VOLUMES}") { + sh "${mvnHome}/bin/mvn -f ./distributed/pom.xml --batch-mode -V -U -e -Dmaven.test.failure.ignore=true clean package -Dsurefire.useFile=false -DskipTests=false" + + } + } + if (currentBuild.previousBuild == null || currentBuild.previousBuild.result != currentBuild.result) { + slackSend(color: '#00FF00', message: "SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + + } catch (e) { + currentBuild.result = 'FAILURE' + if (currentBuild.previousBuild == null || currentBuild.previousBuild.result != currentBuild.result) { + slackSend(color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") + } + throw e; + } + } + +} + diff --git a/OrientDB_logo.svg b/OrientDB_logo.svg new file mode 100644 index 00000000000..5f87b150c2c --- /dev/null +++ b/OrientDB_logo.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 26f1d092d32..e3a9a796980 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,54 @@ -# ETL - -The OrientDB-ETL module is an amazing tool to move data from and to OrientDB by executing an [ETL process](http://en.wikipedia.org/wiki/Extract,_transform,_load). It's super easy to use. OrientDB ETL is based on the following principles: -- one [configuration file](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Configuration-File.html) in [JSON](http://en.wikipedia.org/wiki/JSON) format -- one [Extractor](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Extractor.html) is allowed to extract data from a source -- one [Loader](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Loader.html) is allowed to load data to a destination -- multiple [Transformers](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Transformer.html) that transform data in pipeline. They receive something in input, do something, return something as output that will be processed as input by the next component - -## How ETL works -``` -EXTRACTOR => TRANSFORMERS[] => LOADER -``` -Example of a process that extract from a CSV file, apply some change, lookup if the record has already been created and then store the record as document against OrientDB database: - -``` -+-----------+-----------------------+-----------+ -| | PIPELINE | -+ EXTRACTOR +-----------------------+-----------+ -| | TRANSFORMERS | LOADER | -+-----------+-----------------------+-----------+ -| FILE ==> CSV->FIELD->MERGE ==> OrientDB | -+-----------+-----------------------+-----------+ -``` - -The pipeline, made of transformation and loading phases, can run in parallel by setting the configuration ```{"parallel":true}```. - -## Installation -Starting from OrientDB v2.0 the ETL module will be distributed in bundle with the official release. If you want to use it, then follow these steps: -- Clone the repository on your computer, by executing: - - ```git clone https://github.com/orientechnologies/orientdb-etl.git``` -- Compile the module, by executing: - - ```mvn clean install``` -- Copy ```script/oetl.sh``` (or .bat under Windows) to $ORIENTDB_HOME/bin -- Copy ```target/orientdb-etl-2.0-SNAPSHOT.jar``` to $ORIENTDB_HOME/lib - -## Usage - -``` -$ cd $ORIENTDB_HOME/bin -$ ./oetl.sh config-dbpedia.json -``` - -## Available Components -- [Blocks](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Block.html) -- [Sources](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Source.html) -- [Extractors](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Extractor.html) -- [Transformers](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Transformer.html) -- [Loaders](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Loader.html) - -Examples: -- [Import DBPedia](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Import-from-DBPedia.html) -- [Import from a DBMS](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Import-from-DBMS.html) - - -Look to the [Documentation](http://www.orientechnologies.com/docs/last/orientdb-etl.wiki/Introduction.html) for more information. +## OrientDB + +**Develop branch:** [![Build Status](http://helios.orientdb.com/view/multibranch/job/orientdb-multibranch/job/develop/badge/icon)](http://helios.orientdb.com/view/multibranch/job/orientdb-multibranch/job/develop/) **2.2.x branch:** [![Build Status](http://helios.orientdb.com/view/multibranch/job/orientdb-multibranch/job/2.2.x/badge/icon)](http://helios.orientdb.com/view/multibranch/job/orientdb-multibranch/job/2.2.x/) **Chat with the community:** [![Gitter chat](https://badges.gitter.im/orientechnologies/orientdb.png)](https://gitter.im/orientechnologies/orientdb) + +------ + + + +## What is OrientDB? + +**OrientDB** is an Open Source Multi-Model [NoSQL](http://en.wikipedia.org/wiki/NoSQL) DBMS with the support of Native Graphs, Documents Full-Text, Reactivity, Geo-Spatial and Object Oriented concepts. It's written in Java and it's amazingly fast: it can store 220,000 records per second on common hardware. No expensive run-time JOINs, connections are managed as persistent pointers between records. You can traverse thousands of records in a few milliseconds. Supports schema-less, schema-full and schema-mixed modes. Has a strong security profiling system based on user and roles and supports [SQL](http://orientdb.com/docs/last/SQL.html) amongst the query languages. Thanks to the [SQL](http://orientdb.com/docs/last/SQL.html) layer it's straightforward to use for people skilled in the Relational world. + +[Get started with OrientDB](http://orientdb.com/getting-started/). + +## Is OrientDB a Relational DBMS? + +No. OrientDB adheres to the [NoSQL](http://en.wikipedia.org/wiki/NoSQL) movement even though it supports [ACID Transactions](https://orientdb.com/docs/2.2/Transactions.html) and [SQL](http://orientdb.com/docs/last/SQL.html) as query language. In this way it's easy to start using it without having to learn too much new stuff. + +## Scalability: the database is the bottleneck of most applications + +The most common reason applications scale out badly is, very often, the database. The database is the bottleneck of most applications. OrientDB scales out very well on multiple machines, thanks to the Multi-Master replication where there is no single bottleneck on writes like with Master-Slave replication. The database can be up to 302,231,454,903,657 billion (2^78) records for the maximum capacity of 19,807,040,628,566,084 Terabytes of data on a single server or multiple nodes. + +## I can't believe it! Why is it so fast? + +OrientDB has been designed to be very fast. It inherits the best features and concepts from Object Databases, Graph DBMS and modern [NoSQL](http://en.wikipedia.org/wiki/NoSQL) engines. OrientDB manages relationships without the run-time costly join operation, but rather with direct pointers (links) between records. No matters if you have 1 or 1,000 Billion of records, the traversing cost remains constant. Download the Benchmark PDF XGDBench: A Benchmarking Platform for Graph Stores in Exascale Clouds by Tokyo Institute of Technology and IBM Research. + +## How does it compare with other products? + +As Multi-Model DBMS, OrientDB could work as an extended Document Database and an extended Graph Database. Take a look at [OrientDB vs MongoDB](http://orientdb.com/orientdb-vs-mongodb/) for Document Databases and [OrientDB vs Neo4j](http://orientdb.com/orientdb-vs-neo4j/) to have a comparison with a popular Graph Database. + +## Easy to install and use + +Yes. OrientDB is totally written in [Java](http://en.wikipedia.org/wiki/Java_%28programming_language%29) and can run on any platform without configuration and installation. The full Server distribution is a few MBs without the demo database. Do you develop with a language different than Java? No problem, look at the [Programming Language Binding](http://orientdb.com/docs/last/Programming-Language-Bindings.html). + +## Professional services + +OrientDB Community Edition is free for any use (Apache 2 license). If you are in production don't miss the [Enterprise Edition](http://orientdb.com/orientdb-enterprise/) and [professional support service](http://orientdb.com/support/). For courses and training look at the [on-line course catalog](http://orientdb.com/training/). + +## Main References +- [Documentation](http://orientdb.com/docs/last/) +- For any questions visit the [OrientDB Community Group](http://orientdb.com/active-user-community/) +- [Professional Support](orientdb.com/support/). + +[Get started with OrientDB](http://orientdb.com/getting-started/). + +-------- +### Sponsors + +|[![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/.net/profiler/index.jsp)|YourKit supports open source projects with its full-featured Java Profiler. YourKit, LLC is the creator of YourKit Java Profiler and YourKit .NET Profiler, innovative and intelligent tools for profiling Java and .NET applications.| +|---|---| + +-------- + +[![](http://www.softpedia.com/_img/softpedia_100_free.png)](http://mac.softpedia.com/get/Developer-Tools/Orient.shtml) diff --git a/_base/base-build.xml b/_base/base-build.xml new file mode 100644 index 00000000000..143dc67748e --- /dev/null +++ b/_base/base-build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ----------------------------------- + -> ${ant.project.name} + ----------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_base/ide/eclipse-formatter.xml b/_base/ide/eclipse-formatter.xml new file mode 100644 index 00000000000..d689dd35d4d --- /dev/null +++ b/_base/ide/eclipse-formatter.xml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_base/ide/idea-formatter.jar b/_base/ide/idea-formatter.jar new file mode 100644 index 00000000000..e019db28287 Binary files /dev/null and b/_base/ide/idea-formatter.jar differ diff --git a/_base/images/orient_db.png b/_base/images/orient_db.png new file mode 100644 index 00000000000..7ff9edab82f Binary files /dev/null and b/_base/images/orient_db.png differ diff --git a/_base/lib/UmlGraph.jar b/_base/lib/UmlGraph.jar new file mode 100644 index 00000000000..45ce617f1f9 Binary files /dev/null and b/_base/lib/UmlGraph.jar differ diff --git a/_base/script/cd.sh b/_base/script/cd.sh new file mode 100755 index 00000000000..630fee45141 --- /dev/null +++ b/_base/script/cd.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +export VER=`grep "" pom.xml | head -n 1|awk -F "[<>]" '{print $3}'` + +if [ -z "$VER" ] +then + echo "ERROR: CANNOT FIND CURRENT ORIENTDB RELEASE!" + exit +fi + +echo "Switching to the fresh built OrientDB $VER" + +DIR=distribution/target/orientdb-community-$VER.dir/orientdb-community-$VER/ + +cd $DIR diff --git a/_base/script/deploy.sh b/_base/script/deploy.sh new file mode 100755 index 00000000000..87f91e98ee9 --- /dev/null +++ b/_base/script/deploy.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +export VER=`grep "" pom.xml | head -n 1|awk -F "[<>]" '{print $3}'` + +if [ -z "$VER" ] +then + echo "ERROR: CANNOT FIND CURRENT ORIENTDB RELEASE!" + exit +fi + +echo "Building OrientDB $VER..." + +mvn clean install -DskipTests=true -Dmaven.findbugs.enable=false -DlocalDeploy=true + +DIR=distribution/target/orientdb-community-$VER.dir/orientdb-community-$VER/ + +echo "Releasing OrientDB $VER to $DIR..." + +cd $DIR + +if [ -n "$1" ] +then + echo "Linking databases folder in $1..." + rm -rf databases + ln -s $1 databases +fi + +echo "Switching to the fresh built OrientDB $VER" + diff --git a/_base/script/fix-eclipse.sh b/_base/script/fix-eclipse.sh new file mode 100755 index 00000000000..98f3289f4af --- /dev/null +++ b/_base/script/fix-eclipse.sh @@ -0,0 +1,41 @@ +#!/bin/bash +if [ -z "$1" ] +then + echo "SYNTAX ERROR, USE: fix-eclipse.sh " + exit +fi + +echo "Updating version in Eclipse .classpath files..." + +TFILE="/tmp/out.tmp.$$" +for filename in $(grep -r "J2SE-1.5" --include "*.classpath" $1|cut -f 1 -d :) +do + if [ -f $filename -a -r $filename ]; then + #/bin/cp -f $filename ${filename}.old + sed "s/J2SE-1.5/JavaSE-1.6/g" "$filename" > $TFILE && mv $TFILE "$filename" + else + echo "Error: Cannot read $filename" + fi +done + +for filename in $(grep -r "1.5" --include "*/.settings/org.eclipse.jdt.core.prefs" $1|cut -f 1 -d :) +do + if [ -f $filename -a -r $filename ]; then + #/bin/cp -f $filename ${filename}.old + rm "$filename" > $TFILE && mv $TFILE "$filename" + else + echo "Error: Cannot read $filename" + fi +done + +for filename in $(find $1 -name bin -print) +do + if [ -d $filename ]; then + #/bin/cp -f $filename ${filename}.old + rm -rf "$filename" > $TFILE && mv $TFILE "$filename" + else + echo "Error: Cannot read $filename" + fi +done + +/bin/rm $TFILE 2>/dev/null diff --git a/_base/script/release.sh b/_base/script/release.sh new file mode 100755 index 00000000000..3e9210d8ea3 --- /dev/null +++ b/_base/script/release.sh @@ -0,0 +1,25 @@ +#!/bin/bash +if [ -z "$1" ] +then + echo "SYNTAX ERROR, USE: release.sh " + exit +fi + +echo "Releasing OrientDB $1..." + +DIR=distribution/target/orientdb-community-$1-distribution.dir/orientdb-community-$1/ + +#cp ../drivers/orientdb-jdbc/target/orientdb-jdbc-$1.jar $DIR/lib/ + +#cp ../modules/orientdb-spatial/target/orientdb-spatial-$1-dist.jar $DIR/plugins/ + +#cp ../modules/orientdb-etl/target/orientdb-etl-$1.jar $DIR/lib/ +#cp ../modules/orientdb-etl/script/oetl.* $DIR/bin/ + +cd distribution/target/orientdb-community-$1-distribution.dir + +rm orientdb-community-$1.tar.gz +tar cvzf ../orientdb-community-$1.tar.gz orientdb-community-$1 + +rm orientdb-community-$1.zip +zip -X -r -9 ../orientdb-community-$1.zip orientdb-community-$1 diff --git a/_base/script/release_community.sh b/_base/script/release_community.sh new file mode 100755 index 00000000000..de02ffabce5 --- /dev/null +++ b/_base/script/release_community.sh @@ -0,0 +1,29 @@ +#!/bin/bash +if [ -z "$1" ] +then + echo "SYNTAX ERROR, USE: release.sh " + exit +fi + +echo "Releasing OrientDB $1..." + +DIR=distribution/target/orientdb-community-$1-distribution.dir/orientdb-community-$1/ + +#cp ../drivers/orientdb-jdbc/target/orientdb-jdbc-$1.jar $DIR/lib/ + +#cp ../modules/orientdb-spatial/target/orientdb-spatial-$1-dist.jar $DIR/plugins/ + +#cp ../modules/orientdb-etl/target/orientdb-etl-$1.jar $DIR/lib/ +#cp ../modules/orientdb-etl/script/oetl.* $DIR/bin/ + +cd distribution/target/ + +rm orientdb-community-$1.tar.gz +rm orientdb-community-$1.zip + +cd orientdb-community-$1-distribution.dir +rm `find . -name ".DS_Store" -print` +rm `find . -name "*.wal" -print` + +tar cvzf ../orientdb-community-$1.tar.gz orientdb-community-$1 +zip -X -r -9 ../orientdb-community-$1.zip orientdb-community-$1 diff --git a/_base/script/release_enterprise.sh b/_base/script/release_enterprise.sh new file mode 100755 index 00000000000..08262bd843b --- /dev/null +++ b/_base/script/release_enterprise.sh @@ -0,0 +1,32 @@ +#!/bin/bash +if [ -z "$1" ] +then + echo "SYNTAX ERROR, USE: release.sh " + exit +fi + +echo "Releasing OrientDB Enterprise $1..." + +cp -R distribution/target/orientdb-community-$1-distribution.dir/orientdb-community-$1/ distribution/target/orientdb-community-$1-distribution.dir/orientdb-enterprise-$1/ + +DIR=distribution/target/orientdb-community-$1-distribution.dir/orientdb-enterprise-$1/ + +#cp ../drivers/orientdb-jdbc/target/orientdb-jdbc-$1.jar $DIR/lib/ + +cp ../modules/orientdb-spatial/target/orientdb-spatial-$1-dist.jar $DIR/plugins/ + +#cp ../modules/orientdb-etl/target/orientdb-etl-$1.jar $DIR/lib/ +#cp ../modules/orientdb-etl/script/oetl.* $DIR/bin/ + +cp ../../orientdb-enterprise/agent/target/agent-$1.zip $DIR/plugins/ + +cd distribution/target/ +rm orientdb-enterprise-$1.tar.gz +rm orientdb-enterprise-$1.zip + +cd orientdb-community-$1-distribution.dir +rm `find . -name ".DS_Store" -print` +rm `find . -name "*.wal" -print` + +tar cvzf ../orientdb-enterprise-$1.tar.gz orientdb-enterprise-$1 +zip -X -r -9 ../orientdb-enterprise-$1.zip orientdb-enterprise-$1 diff --git a/_base/script/switch.sh b/_base/script/switch.sh new file mode 100755 index 00000000000..87df8db7d91 --- /dev/null +++ b/_base/script/switch.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +if [ -z "$1" ] +then + echo "ERROR: syntax " + exit +fi + +echo "Switching all OrientDB projects to branch: $1" + +echo +echo "********************************" +echo " KERNEL module" +echo "********************************" + +git checkout $1 +git pull + +cd ../modules + +echo +echo "********************************" +echo " LUCENE module" +echo "********************************" +cd orientdb-lucene +git checkout $1 +git pull + +echo +echo "********************************" +echo " ETL module" +echo "********************************" +cd ../orientdb-etl +git checkout $1 +git pull + +cd ../../drivers + +echo +echo "********************************" +echo " JDBC module" +echo "********************************" +cd orientdb-jdbc +git checkout $1 +git pull diff --git a/_base/script/upd-version.sh b/_base/script/upd-version.sh new file mode 100755 index 00000000000..0a1a622e075 --- /dev/null +++ b/_base/script/upd-version.sh @@ -0,0 +1,20 @@ +#!/bin/bash +if [ -z "$1" -o -z "$2" -o -z "$3" ] +then + echo "SYNTAX ERROR, USE: upd-version.sh " + exit +fi + +echo "Updating version from $2 to $3 in directory $1 and subfolders" + +TFILE="/tmp/out.tmp.$$" +for filename in $(grep -r "$2" --include "*.java" --include "*.htm*" --include "*.xml" --exclude-dir "apidocs" --exclude-dir "target" --exclude-dir "test-output" $1|cut -f 1 -d :) +do + if [ -f $filename -a -r $filename ]; then + #/bin/cp -f $filename ${filename}.old + sed "s/$2/$3/g" "$filename" > $TFILE && mv $TFILE "$filename" + else + echo "Error: Cannot read $filename" + fi +done +/bin/rm $TFILE 2>/dev/null diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 00000000000..08fc8ddeb82 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,89 @@ + + + + + + + 4.0.0 + + + com.orientechnologies + orientdb-parent + 2.2.24 + ../ + + + orientdb-client + + OrientDB Client + + + * + com.orientechnologies.orient.client.* + UTF-8 + + -XX:MaxDirectMemorySize=512g + -Dmemory.directMemory.trackMode=true + -Djava.util.logging.manager=com.orientechnologies.common.log.OLogManager$DebugLogManager + -Dstorage.diskCache.checksumMode=storeAndThrow + + + + + + com.orientechnologies + orientdb-core + ${project.version} + + + com.orientechnologies + orientdb-test-commons + ${project.version} + test + + + com.orientechnologies + orientdb-core + ${project.version} + test-jar + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + listener + com.orientechnologies.OTestNGTestLeaksListener + + + + + + + + diff --git a/client/src/main/java/com/orientechnologies/orient/client/binary/OAsynchChannelServiceThread.java b/client/src/main/java/com/orientechnologies/orient/client/binary/OAsynchChannelServiceThread.java new file mode 100644 index 00000000000..5d8c9d85361 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/binary/OAsynchChannelServiceThread.java @@ -0,0 +1,83 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.binary; + +import com.orientechnologies.common.thread.OSoftThread; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.ORemoteServerEventListener; + +import java.io.IOException; + +/** + * Service thread that catches internal messages sent by the server + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OAsynchChannelServiceThread extends OSoftThread { + private OChannelBinaryAsynchClient network; + private int sessionId; + private ORemoteServerEventListener remoteServerEventListener; + + public OAsynchChannelServiceThread(final ORemoteServerEventListener iRemoteServerEventListener, + final OChannelBinaryAsynchClient iChannel) { + super(Orient.instance().getThreadGroup(), "OrientDB <- Asynch Client (" + iChannel.socket.getRemoteSocketAddress() + ")"); + sessionId = Integer.MIN_VALUE; + remoteServerEventListener = iRemoteServerEventListener; + network = iChannel; + setDumpExceptions(false); + start(); + } + + @Override + protected void execute() throws Exception { + try { + Object obj = null; + final byte request; + try { + network.beginResponse(sessionId, 0, false); + request = network.readByte(); + switch (request) { + case OChannelBinaryProtocol.REQUEST_PUSH_DISTRIB_CONFIG: + case OChannelBinaryProtocol.REQUEST_PUSH_LIVE_QUERY: + obj = network.readBytes(); + break; + } + } catch (IOException ioe) { + if (network != null) { + final OChannelBinaryAsynchClient n = network; + network = null; + n.close(); + } + throw ioe; + } finally { + if (network != null) + network.endResponse(); + } + + if (remoteServerEventListener != null) + remoteServerEventListener.onRequest(request, obj); + + } catch (IOException ioe) { + // EXCEPTION RECEIVED (THE SOCKET HAS BEEN CLOSED?) ASSURE TO UNLOCK THE READ AND EXIT THIS THREAD + sendShutdown(); + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClient.java b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClient.java new file mode 100755 index 00000000000..2b1d1560c25 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClient.java @@ -0,0 +1,523 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.binary; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; + +import com.orientechnologies.common.concur.OTimeoutException; +import com.orientechnologies.common.concur.lock.OInterruptedException; +import com.orientechnologies.common.concur.lock.OLockException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.common.io.OIOException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.client.remote.OStorageRemoteNodeSession; +import com.orientechnologies.orient.client.remote.OStorageRemoteSession; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.serialization.OMemoryInputStream; +import com.orientechnologies.orient.enterprise.channel.OSocketFactory; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.ONetworkProtocolException; +import com.orientechnologies.orient.enterprise.channel.binary.ORemoteServerEventListener; +import com.orientechnologies.orient.enterprise.channel.binary.OResponseProcessingException; + +public class OChannelBinaryAsynchClient extends OChannelBinary { + private int socketTimeout; // IN MS + protected final short srvProtocolVersion; + private final Condition readCondition = getLockRead().getUnderlying().newCondition(); + private final int maxUnreadResponses; + private String serverURL; + private volatile boolean channelRead = false; + private byte currentStatus; + private int currentSessionId; + private volatile OAsynchChannelServiceThread serviceThread; + private volatile long lastUse; + private volatile boolean inUse; + + public OChannelBinaryAsynchClient(final String remoteHost, final int remotePort, final String iDatabaseName, + final OContextConfiguration iConfig, final int iProtocolVersion) throws IOException { + this(remoteHost, remotePort, iDatabaseName, iConfig, iProtocolVersion, null); + } + + public OChannelBinaryAsynchClient(final String remoteHost, final int remotePort, final String iDatabaseName, + final OContextConfiguration iConfig, final int protocolVersion, final ORemoteServerEventListener asynchEventListener) + throws IOException { + super(OSocketFactory.instance(iConfig).createSocket(), iConfig); + try { + + maxUnreadResponses = OGlobalConfiguration.NETWORK_BINARY_READ_RESPONSE_MAX_TIMES.getValueAsInteger(); + serverURL = remoteHost + ":" + remotePort; + if (iDatabaseName != null) + serverURL += "/" + iDatabaseName; + socketTimeout = iConfig.getValueAsInteger(OGlobalConfiguration.NETWORK_SOCKET_TIMEOUT); + + try { + socket.connect(new InetSocketAddress(remoteHost, remotePort), getSocketTimeout()); + setReadResponseTimeout(); + connected(); + } catch (java.net.SocketTimeoutException e) { + throw new IOException("Cannot connect to host " + remoteHost + ":" + remotePort, e); + } + try { + if (socketBufferSize > 0) { + inStream = new BufferedInputStream(socket.getInputStream(), socketBufferSize); + outStream = new BufferedOutputStream(socket.getOutputStream(), socketBufferSize); + } else { + inStream = new BufferedInputStream(socket.getInputStream()); + outStream = new BufferedOutputStream(socket.getOutputStream()); + } + + in = new DataInputStream(inStream); + out = new DataOutputStream(outStream); + + srvProtocolVersion = readShort(); + } catch (IOException e) { + throw new ONetworkProtocolException( + "Cannot read protocol version from remote server " + socket.getRemoteSocketAddress() + ": " + e); + } + + if (srvProtocolVersion != protocolVersion) { + OLogManager.instance().warn(this, + "The Client driver version is different than Server version: client=" + protocolVersion + ", server=" + + srvProtocolVersion + + ". You could not use the full features of the newer version. Assure to have the same versions on both"); + } + + if (asynchEventListener != null) + serviceThread = new OAsynchChannelServiceThread(asynchEventListener, this); + } catch (RuntimeException e) { + if (socket.isConnected()) + socket.close(); + throw e; + } + } + + @SuppressWarnings("unchecked") + private static RuntimeException createException(final String iClassName, final String iMessage, final Exception iPrevious) { + RuntimeException rootException = null; + Constructor c = null; + try { + final Class excClass = (Class) Class.forName(iClassName); + if (iPrevious != null) { + try { + c = excClass.getConstructor(String.class, Throwable.class); + } catch (NoSuchMethodException e) { + c = excClass.getConstructor(String.class, Exception.class); + } + } + + if (c == null) + c = excClass.getConstructor(String.class); + + } catch (Exception e) { + // UNABLE TO REPRODUCE THE SAME SERVER-SIDE EXCEPTION: THROW AN SYSTEM EXCEPTION + rootException = OException.wrapException(new OSystemException(iMessage), iPrevious); + } + + if (c != null) + try { + final Exception cause; + if (c.getParameterTypes().length > 1) + cause = (Exception) c.newInstance(iMessage, iPrevious); + else + cause = (Exception) c.newInstance(iMessage); + + rootException = OException.wrapException(new OSystemException("Data processing exception"), cause); + } catch (InstantiationException ignored) { + } catch (IllegalAccessException ignored) { + } catch (InvocationTargetException ignored) { + } + + return rootException; + } + + public byte[] beginResponse(final int iRequesterId, final boolean token) throws IOException { + return beginResponse(iRequesterId, timeout, token); + } + + public byte[] beginResponse(final int iRequesterId, final long iTimeout, final boolean token) throws IOException { + try { + int unreadResponse = 0; + final long startClock = iTimeout > 0 ? System.currentTimeMillis() : 0; + + // WAIT FOR THE RESPONSE + do { + if (iTimeout <= 0) + acquireReadLock(); + else if (!getLockRead().tryAcquireLock(iTimeout, TimeUnit.MILLISECONDS)) + throw new OTimeoutException("Cannot acquire read lock against channel: " + this); + + boolean readLock = true; + + if (!isConnected()) { + releaseReadLock(); + throw new IOException("Channel is closed"); + } + + if (!channelRead) { + channelRead = true; + + try { + setWaitResponseTimeout(); + currentStatus = readByte(); + currentSessionId = readInt(); + + if (debug) + OLogManager.instance() + .debug(this, "%s - Read response: %d-%d", socket.getLocalAddress(), (int) currentStatus, currentSessionId); + + } catch (IOException e) { + // UNLOCK THE RESOURCE AND PROPAGATES THE EXCEPTION + channelRead = false; + readCondition.signalAll(); + releaseReadLock(); + readLock = false; + + throw e; + } finally { + setReadResponseTimeout(); + } + } + + if (currentSessionId == iRequesterId) + // IT'S FOR ME + break; + + if (iRequesterId != Integer.MIN_VALUE && currentStatus != OChannelBinaryProtocol.PUSH_DATA) { + // Is not the push thread and is not skipping the push data + throw new IOException("Dirty data in the socket, retry"); + } + + try { + if (debug) + OLogManager.instance() + .debug(this, "%s - Session %d skip response, it is for %d", socket.getLocalAddress(), iRequesterId, + currentSessionId); + + if (iTimeout > 0 && (System.currentTimeMillis() - startClock) > iTimeout) { + readLock = false; + + throw new IOException( + "Timeout on reading response from the server " + (socket != null ? socket.getRemoteSocketAddress() : "") + + " for the request " + iRequesterId); + } + + // IN CASE OF TOO MUCH TIME FOR READ A MESSAGE, ASYNC THREAD SHOULD NOT BE INCLUDE IN THIS CHECK + if (unreadResponse > maxUnreadResponses && iRequesterId != Integer.MIN_VALUE) { + if (debug) + OLogManager.instance().info(this, "Unread responses %d > %d, consider the buffer as dirty: clean it", unreadResponse, + maxUnreadResponses); + + readLock = false; + + throw new IOException("Timeout on reading response"); + } + + readCondition.signalAll(); + + if (debug) + OLogManager.instance().debug(this, "Session %d is going to sleep...", iRequesterId); + + final long start = System.currentTimeMillis(); + + // WAIT MAX 30 SECOND AND RETRY, THIS IS UNBLOCKED BY ANOTHER THREAD IN CASE THE RESPONSE FOR THIS IS ARRIVED + readCondition.await(30, TimeUnit.SECONDS); + + if (debug) { + final long now = System.currentTimeMillis(); + OLogManager.instance() + .debug(this, "Waked up: slept %dms, checking again from %s for session %d", (now - start), socket.getLocalAddress(), + iRequesterId); + } + + unreadResponse++; + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw OException.wrapException(new OInterruptedException("Thread interrupted while waiting for request"), e); + } finally { + if (readLock) + releaseReadLock(); + } + } while (true); + + if (debug) + OLogManager.instance().debug(this, "%s - Session %d handle response", socket.getLocalAddress(), iRequesterId); + byte[] tokenBytes; + if (token) + tokenBytes = this.readBytes(); + else + tokenBytes = null; + handleStatus(currentStatus, currentSessionId); + return tokenBytes; + } catch (OLockException e) { + Thread.currentThread().interrupt(); + // NEVER HAPPENS? + OLogManager.instance().error(this, "Unexpected error on reading response from channel", e); + } + return null; + } + + public void endResponse() throws IOException { + channelRead = false; + + // WAKE UP ALL THE WAITING THREADS + try { + readCondition.signalAll(); + } catch (IllegalMonitorStateException e) { + // IGNORE IT + OLogManager.instance().debug(this, "Error on signaling waiting clients after reading response"); + } + try { + releaseReadLock(); + } catch (IllegalMonitorStateException e) { + // IGNORE IT + OLogManager.instance().debug(this, "Error on unlocking network channel after reading response"); + } + + } + + @Override + public void close() { + if (getLockRead().tryAcquireLock()) + try { + readCondition.signalAll(); + } finally { + releaseReadLock(); + } + + try { + super.close(); + } catch (Exception e) { + // IGNORE IT + } + + if (serviceThread != null) { + final OAsynchChannelServiceThread s = serviceThread; + serviceThread = null; + if (s != null) + // CHECK S BECAUSE IT COULD BE CONCURRENTLY RESET + s.sendShutdown(); + } + } + + @Override + public void clearInput() throws IOException { + acquireReadLock(); + try { + super.clearInput(); + } finally { + releaseReadLock(); + } + } + + /** + * Tells if the channel is connected. + * + * @return true if it's connected, otherwise false. + */ + public boolean isConnected() { + final Socket s = socket; + return s != null && !s.isClosed() && s.isConnected() && !s.isInputShutdown() && !s.isOutputShutdown(); + } + + /** + * Gets the major supported protocol version + */ + public short getSrvProtocolVersion() { + return srvProtocolVersion; + } + + public String getServerURL() { + return serverURL; + } + + public boolean tryLock() { + return getLockWrite().tryAcquireLock(); + } + + public void unlock() { + getLockWrite().unlock(); + } + + public OAsynchChannelServiceThread getServiceThread() { + return serviceThread; + } + + protected int handleStatus(final byte iResult, final int iClientTxId) throws IOException { + if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_OK || iResult == OChannelBinaryProtocol.PUSH_DATA) { + return iClientTxId; + } else if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_ERROR) { + + final List> exceptions = new ArrayList>(); + + // EXCEPTION + while (readByte() == 1) { + final String excClassName = readString(); + final String excMessage = readString(); + exceptions.add(new OPair(excClassName, excMessage)); + } + + byte[] serializedException = null; + if (srvProtocolVersion >= 19) + serializedException = readBytes(); + + Exception previous = null; + + if (serializedException != null && serializedException.length > 0) + throwSerializedException(serializedException); + + for (int i = exceptions.size() - 1; i > -1; --i) { + previous = createException(exceptions.get(i).getKey(), exceptions.get(i).getValue(), previous); + } + + if (previous != null) { + throw new RuntimeException(previous); + } else + throw new ONetworkProtocolException("Network response error"); + + } else { + // PROTOCOL ERROR + // close(); + throw new ONetworkProtocolException("Error on reading response from the server"); + } + } + + private void setReadResponseTimeout() throws SocketException { + final Socket s = socket; + if (s != null && s.isConnected() && !s.isClosed()) + s.setSoTimeout(getSocketTimeout()); + } + + private void setWaitResponseTimeout() throws SocketException { + final Socket s = socket; + if (s != null) + s.setSoTimeout(OGlobalConfiguration.NETWORK_REQUEST_TIMEOUT.getValueAsInteger()); + } + + private void throwSerializedException(final byte[] serializedException) throws IOException { + final OMemoryInputStream inputStream = new OMemoryInputStream(serializedException); + final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); + + Object throwable = null; + try { + throwable = objectInputStream.readObject(); + } catch (ClassNotFoundException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + throw new IOException("Error during exception deserialization: " + e.toString()); + } + + objectInputStream.close(); + + if (throwable instanceof OException) { + try { + final Class cls = (Class) throwable.getClass(); + final Constructor constructor; + constructor = cls.getConstructor(cls); + final OException proxyInstance = constructor.newInstance(throwable); + + throw proxyInstance; + + } catch (NoSuchMethodException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (InvocationTargetException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (InstantiationException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (IllegalAccessException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } + } + + if (throwable instanceof Throwable) { + throw new OResponseProcessingException("Exception during response processing", (Throwable) throwable); + } + // WRAP IT + else + OLogManager.instance().error(this, + "Error during exception serialization, serialized exception is not Throwable, exception type is " + (throwable != null ? + throwable.getClass().getName() : + "null")); + } + + public void beginRequest(final byte iCommand, final OStorageRemoteSession session) throws IOException { + final OStorageRemoteNodeSession nodeSession = session.getServerSession(getServerURL()); + + if (nodeSession == null) + throw new OIOException("Invalid session for URL '" + getServerURL() + "'"); + + writeByte(iCommand); + writeInt(nodeSession.getSessionId()); + if (nodeSession.getToken() != null) { + // if (!session.hasConnection(this) || true) { + writeBytes(nodeSession.getToken()); + // session.addConnection(this); + // } else + // writeBytes(new byte[] {}); + } + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + private void markLastUse() { + lastUse = System.currentTimeMillis(); + } + + public long getLastUse() { + return lastUse; + } + + public void markReturned() { + markLastUse(); + inUse = false; + } + + public void markInUse() { + markLastUse(); + inUse = false; + } + + public boolean isInUse() { + return inUse; + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClientSynch.java b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClientSynch.java new file mode 100644 index 00000000000..a8616b4a778 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryAsynchClientSynch.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.binary; + +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; + +import java.io.IOException; + +/** + * Single requester client implementation. + * + * @author Luca + * + */ +public class OChannelBinaryAsynchClientSynch extends OChannelBinaryAsynchClient { + + public OChannelBinaryAsynchClientSynch(final String remoteHost, final int remotePort, final String iDatabaseName, final OContextConfiguration iConfig) + throws IOException { + super(remoteHost, remotePort,iDatabaseName, iConfig, OChannelBinaryProtocol.CURRENT_PROTOCOL_VERSION); + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryClientAbstract.java b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryClientAbstract.java new file mode 100755 index 00000000000..f05ac2e6a2b --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinaryClientAbstract.java @@ -0,0 +1,282 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.binary; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.serialization.OMemoryInputStream; +import com.orientechnologies.orient.enterprise.channel.OSocketFactory; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.ONetworkProtocolException; +import com.orientechnologies.orient.enterprise.channel.binary.OResponseProcessingException; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract implementation of binary channel. + */ +public abstract class OChannelBinaryClientAbstract extends OChannelBinary { + protected final int socketTimeout; // IN MS + protected final short srvProtocolVersion; + protected String serverURL; + protected byte currentStatus; + protected int currentSessionId; + + public OChannelBinaryClientAbstract(final String remoteHost, final int remotePort, final String iDatabaseName, + final OContextConfiguration iConfig, final int protocolVersion) throws IOException { + super(OSocketFactory.instance(iConfig).createSocket(), iConfig); + try { + + serverURL = remoteHost + ":" + remotePort; + if (iDatabaseName != null) + serverURL += "/" + iDatabaseName; + socketTimeout = iConfig.getValueAsInteger(OGlobalConfiguration.NETWORK_SOCKET_TIMEOUT); + + try { + if (remoteHost.contains(":")) { + // IPV6 + final InetAddress[] addresses = Inet6Address.getAllByName(remoteHost); + socket.connect(new InetSocketAddress(addresses[0], remotePort), socketTimeout); + + } else { + // IPV4 + socket.connect(new InetSocketAddress(remoteHost, remotePort), socketTimeout); + } + setReadResponseTimeout(); + connected(); + } catch (java.net.SocketTimeoutException e) { + throw new IOException("Cannot connect to host " + remoteHost + ":" + remotePort + " (timeout=" + socketTimeout + ")", e); + } + try { + if (socketBufferSize > 0) { + inStream = new BufferedInputStream(socket.getInputStream(), socketBufferSize); + outStream = new BufferedOutputStream(socket.getOutputStream(), socketBufferSize); + } else { + inStream = new BufferedInputStream(socket.getInputStream()); + outStream = new BufferedOutputStream(socket.getOutputStream()); + } + + in = new DataInputStream(inStream); + out = new DataOutputStream(outStream); + + srvProtocolVersion = readShort(); + } catch (IOException e) { + throw new ONetworkProtocolException( + "Cannot read protocol version from remote server " + socket.getRemoteSocketAddress() + ": " + e); + } + + if (srvProtocolVersion != protocolVersion) { + OLogManager.instance().warn(this, + "The Client driver version is different than Server version: client=" + protocolVersion + ", server=" + + srvProtocolVersion + + ". You could not use the full features of the newer version. Assure to have the same versions on both"); + } + + } catch (RuntimeException e) { + if (socket.isConnected()) + socket.close(); + throw e; + } + } + + @SuppressWarnings("unchecked") + private static RuntimeException createException(final String iClassName, final String iMessage, final Exception iPrevious) { + RuntimeException rootException = null; + Constructor c = null; + try { + final Class excClass = (Class) Class.forName(iClassName); + if (iPrevious != null) { + try { + c = excClass.getConstructor(String.class, Throwable.class); + } catch (NoSuchMethodException e) { + c = excClass.getConstructor(String.class, Exception.class); + } + } + + if (c == null) + c = excClass.getConstructor(String.class); + + } catch (Exception e) { + // UNABLE TO REPRODUCE THE SAME SERVER-SIZE EXCEPTION: THROW AN IO EXCEPTION + rootException = OException.wrapException(new OSystemException(iMessage), iPrevious); + } + + if (c != null) + try { + final Exception cause; + if (c.getParameterTypes().length > 1) + cause = (Exception) c.newInstance(iMessage, iPrevious); + else + cause = (Exception) c.newInstance(iMessage); + + rootException = OException.wrapException(new OSystemException("Data processing exception"), cause); + } catch (InstantiationException ignored) { + } catch (IllegalAccessException ignored) { + } catch (InvocationTargetException ignored) { + } + + return rootException; + } + + @Override + public void clearInput() throws IOException { + acquireReadLock(); + try { + super.clearInput(); + } finally { + releaseReadLock(); + } + } + + /** + * Tells if the channel is connected. + * + * @return true if it's connected, otherwise false. + */ + public boolean isConnected() { + final Socket s = socket; + return s != null && !s.isClosed() && s.isConnected() && !s.isInputShutdown() && !s.isOutputShutdown(); + } + + /** + * Gets the major supported protocol version + * + */ + public short getSrvProtocolVersion() { + return srvProtocolVersion; + } + + public String getServerURL() { + return serverURL; + } + + public boolean tryLock() { + return getLockWrite().tryAcquireLock(); + } + + public void unlock() { + getLockWrite().unlock(); + } + + protected int handleStatus(final byte iResult, final int iClientTxId) throws IOException { + if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_OK || iResult == OChannelBinaryProtocol.PUSH_DATA) { + return iClientTxId; + } else if (iResult == OChannelBinaryProtocol.RESPONSE_STATUS_ERROR) { + + final List> exceptions = new ArrayList>(); + + // EXCEPTION + while (readByte() == 1) { + final String excClassName = readString(); + final String excMessage = readString(); + exceptions.add(new OPair(excClassName, excMessage)); + } + + byte[] serializedException = null; + if (srvProtocolVersion >= 19) + serializedException = readBytes(); + + Exception previous = null; + + if (serializedException != null && serializedException.length > 0) + throwSerializedException(serializedException); + + for (int i = exceptions.size() - 1; i > -1; --i) { + previous = createException(exceptions.get(i).getKey(), exceptions.get(i).getValue(), previous); + } + + if (previous != null) { + throw new RuntimeException(previous); + } else + throw new ONetworkProtocolException("Network response error"); + + } else { + // PROTOCOL ERROR + // close(); + throw new ONetworkProtocolException("Error on reading response from the server"); + } + } + + protected void setReadResponseTimeout() throws SocketException { + final Socket s = socket; + if (s != null && s.isConnected() && !s.isClosed()) + s.setSoTimeout(socketTimeout); + } + + public void setWaitResponseTimeout() throws SocketException { + final Socket s = socket; + if (s != null) + s.setSoTimeout(OGlobalConfiguration.NETWORK_REQUEST_TIMEOUT.getValueAsInteger()); + } + + protected void throwSerializedException(final byte[] serializedException) throws IOException { + final OMemoryInputStream inputStream = new OMemoryInputStream(serializedException); + final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); + + Object throwable = null; + try { + throwable = objectInputStream.readObject(); + } catch (ClassNotFoundException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + throw new IOException("Error during exception deserialization: " + e.toString()); + } + + objectInputStream.close(); + + if (throwable instanceof OException) { + try { + final Class cls = (Class) throwable.getClass(); + final Constructor constructor; + constructor = cls.getConstructor(cls); + final OException proxyInstance = constructor.newInstance(throwable); + + throw proxyInstance; + + } catch (NoSuchMethodException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (InvocationTargetException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (InstantiationException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } catch (IllegalAccessException e) { + OLogManager.instance().error(this, "Error during exception deserialization", e); + } + } + + if (throwable instanceof Throwable) { + throw new OResponseProcessingException("Exception during response processing", (Throwable) throwable); + } + // WRAP IT + else + OLogManager.instance().error(this, + "Error during exception serialization, serialized exception is not Throwable, exception type is " + + (throwable != null ? throwable.getClass().getName() : "null")); + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinarySynchClient.java b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinarySynchClient.java new file mode 100755 index 00000000000..2974fe7f254 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/binary/OChannelBinarySynchClient.java @@ -0,0 +1,59 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.binary; + +import com.orientechnologies.orient.core.config.OContextConfiguration; + +import java.io.IOException; + +/** + * Synchronous implementation of binary channel. + */ +public class OChannelBinarySynchClient extends OChannelBinaryClientAbstract { + public OChannelBinarySynchClient(final String remoteHost, final int remotePort, final String iDatabaseName, + final OContextConfiguration iConfig, final int protocolVersion) throws IOException { + super(remoteHost, remotePort, iDatabaseName, iConfig, protocolVersion); + } + + public void beginRequest(final byte iCommand, final int sessionId, final byte[] token) throws IOException { + writeByte(iCommand); + writeInt(sessionId); + if (token != null) { + if (sessionId == -1) + // SEND THE TOKEN ONLY THE FIRST TIME + writeBytes(token); + else + writeBytes(new byte[] {}); + } + } + + public byte[] beginResponse(final boolean token) throws IOException { + currentStatus = readByte(); + currentSessionId = readInt(); + + byte[] tokenBytes; + if (token) + tokenBytes = this.readBytes(); + else + tokenBytes = null; + handleStatus(currentStatus, currentSessionId); + return tokenBytes; + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/db/ODatabaseHelper.java b/client/src/main/java/com/orientechnologies/orient/client/db/ODatabaseHelper.java new file mode 100755 index 00000000000..3bdea01980f --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/db/ODatabaseHelper.java @@ -0,0 +1,189 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.db; + +import com.orientechnologies.common.parser.OSystemVariableResolver; +import com.orientechnologies.orient.client.remote.OEngineRemote; +import com.orientechnologies.orient.client.remote.OServerAdmin; +import com.orientechnologies.orient.core.OConstants; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.OConfigurationException; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class ODatabaseHelper { + public static void createDatabase(ODatabase database, final String url) throws IOException { + createDatabase(database, url, "server", "plocal"); + } + + public static void createDatabase(ODatabase database, final String url, String type) throws IOException { + createDatabase(database, url, "server", type); + } + + public static void openDatabase(ODatabase database) { + database.open("admin", "admin"); + } + + public static void createDatabase(ODatabase database, final String url, String directory, String type) throws IOException { + if (url.startsWith(OEngineRemote.NAME)) { + new OServerAdmin(url).connect("root", getServerRootPassword(directory)).createDatabase("document", type).close(); + } else { + database.create(); + database.close(); + } + } + + public static void deleteDatabase(final ODatabase database, String storageType) throws IOException { + deleteDatabase(database, "server", storageType); + } + + @Deprecated + public static void deleteDatabase(final ODatabase database, final String directory, String storageType) throws IOException { + dropDatabase(database, directory, storageType); + } + + public static void dropDatabase(final ODatabase database, String storageType) throws IOException { + dropDatabase(database, "server", storageType); + } + + public static void dropDatabase(final ODatabase database, final String directory, String storageType) throws IOException { + if (existsDatabase(database, storageType)) { + if (database.getURL().startsWith("remote:")) { + database.activateOnCurrentThread(); + database.close(); + OServerAdmin admin = new OServerAdmin(database.getURL()).connect("root", getServerRootPassword(directory)); + admin.dropDatabase(storageType); + admin.close(); + } else { + if (database.isClosed()) + openDatabase(database); + else + database.activateOnCurrentThread(); + database.drop(); + } + } + } + + public static boolean existsDatabase(final ODatabase database, String storageType) throws IOException { + database.activateOnCurrentThread(); + if (database.getURL().startsWith("remote")) { + OServerAdmin admin = new OServerAdmin(database.getURL()).connect("root", getServerRootPassword()); + boolean exist = admin.existsDatabase(storageType); + admin.close(); + return exist; + } + + return database.exists(); + } + + public static boolean existsDatabase(final String url) throws IOException { + if (url.startsWith("remote")) { + OServerAdmin admin = new OServerAdmin(url).connect("root", getServerRootPassword()); + boolean exist = admin.existsDatabase(); + admin.close(); + return exist; + } + return new ODatabaseDocumentTx(url).exists(); + } + + public static void freezeDatabase(final ODatabase database) throws IOException { + database.activateOnCurrentThread(); + if (database.getURL().startsWith("remote")) { + final OServerAdmin serverAdmin = new OServerAdmin(database.getURL()); + serverAdmin.connect("root", getServerRootPassword()).freezeDatabase("plocal"); + serverAdmin.close(); + } else { + database.freeze(); + } + } + + public static void releaseDatabase(final ODatabase database) throws IOException { + database.activateOnCurrentThread(); + if (database.getURL().startsWith("remote")) { + final OServerAdmin serverAdmin = new OServerAdmin(database.getURL()); + serverAdmin.connect("root", getServerRootPassword()).releaseDatabase("plocal"); + serverAdmin.close(); + } else { + database.release(); + } + } + + public static File getConfigurationFile() { + return getConfigurationFile(null); + } + + public static String getServerRootPassword() throws IOException { + return getServerRootPassword("server"); + } + + protected static String getServerRootPassword(final String iDirectory) throws IOException { + String passwd = System.getProperty("ORIENTDB_ROOT_PASSWORD"); + if( passwd!=null) + return passwd; + + final File file = getConfigurationFile(iDirectory); + + final FileReader f = new FileReader(file); + final char[] buffer = new char[(int) file.length()]; + f.read(buffer); + f.close(); + + final String fileContent = new String(buffer); + // TODO search is wrong because if first user is not root tests will fail + int pos = fileContent.indexOf("password=\""); + pos += "password=\"".length(); + return fileContent.substring(pos, fileContent.indexOf('"', pos)); + } + + protected static File getConfigurationFile(final String iDirectory) { + // LOAD SERVER CONFIG FILE TO EXTRACT THE ROOT'S PASSWORD + String sysProperty = System.getProperty("orientdb.config.file"); + File file = new File(sysProperty != null ? sysProperty : ""); + if (!file.exists()) { + sysProperty = System.getenv("CONFIG_FILE"); + file = new File(sysProperty != null ? sysProperty : ""); + } + if (!file.exists()) + file = new File("../releases/orientdb-" + OConstants.ORIENT_VERSION + "/config/orientdb-server-config.xml"); + if (!file.exists()) + file = new File("../releases/orientdb-community-" + OConstants.ORIENT_VERSION + "/config/orientdb-server-config.xml"); + if (!file.exists()) + file = new File("../../releases/orientdb-" + OConstants.ORIENT_VERSION + "/config/orientdb-server-config.xml"); + if (!file.exists()) + file = new File("../../releases/orientdb-community-" + OConstants.ORIENT_VERSION + "/config/orientdb-server-config.xml"); + if (!file.exists() && iDirectory != null) { + file = new File(iDirectory + "/config/orientdb-server-config.xml"); + if (!file.exists()) + file = new File("../" + iDirectory + "/config/orientdb-server-config.xml"); + } + if (!file.exists()) + file = new File( + OSystemVariableResolver.resolveSystemVariables("${" + Orient.ORIENTDB_HOME + "}/config/orientdb-server-config.xml")); + if (!file.exists()) + throw new OConfigurationException( + "Cannot load file orientdb-server-config.xml to execute remote tests. Current directory is " + new File(".") + .getAbsolutePath()); + return file; + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OClusterRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OClusterRemote.java new file mode 100755 index 00000000000..4e9456196fd --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OClusterRemote.java @@ -0,0 +1,255 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.config.OStorageClusterConfiguration; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategy; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.storage.*; + +import java.io.IOException; + +/** + * Remote cluster implementation + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OClusterRemote implements OCluster { + private String name; + private int id; + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.storage.OCluster#configure(com.orientechnologies.orient.core.storage.OStorage, int, + * java.lang.String, java.lang.String, int, java.lang.Object[]) + */ + public void configure(OStorage iStorage, int iId, String iClusterName, Object... iParameters) throws IOException { + id = iId; + name = iClusterName; + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.storage.OCluster#configure(com.orientechnologies.orient.core.storage.OStorage, + * com.orientechnologies.orient.core.config.OStorageClusterConfiguration) + */ + public void configure(OStorage iStorage, OStorageClusterConfiguration iConfig) throws IOException { + id = iConfig.getId(); + name = iConfig.getName(); + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.storage.OCluster#create(int) + */ + public void create(int iStartSize) throws IOException { + + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.storage.OCluster#open() + */ + public void open() throws IOException { + } + + public void close() throws IOException { + } + + @Override + public void close(boolean flush) throws IOException { + } + + @Override + public OPhysicalPosition allocatePosition(byte recordType) throws IOException { + throw new UnsupportedOperationException("allocatePosition"); + } + + @Override + public OPhysicalPosition createRecord(byte[] content, int recordVersion, byte recordType, OPhysicalPosition allocatedPosition) + throws IOException { + throw new UnsupportedOperationException("createRecord"); + } + + @Override + public boolean deleteRecord(long clusterPosition) throws IOException { + throw new UnsupportedOperationException("deleteRecord"); + } + + @Override + public void updateRecord(long clusterPosition, byte[] content, int recordVersion, byte recordType) throws IOException { + throw new UnsupportedOperationException("updateRecord"); + } + + @Override + public void recycleRecord(long clusterPosition) throws IOException { + throw new UnsupportedOperationException("recyclePosition"); + } + + @Override + public ORawBuffer readRecord(long clusterPosition, boolean prefetchRecords) throws IOException { + throw new UnsupportedOperationException("readRecord"); + } + + @Override + public ORawBuffer readRecordIfVersionIsNotLatest(long clusterPosition, int recordVersion) + throws IOException, ORecordNotFoundException { + throw new UnsupportedOperationException("readRecordIfVersionIsNotLatest"); + } + + @Override + public boolean exists() { + throw new UnsupportedOperationException("exists"); + } + + public void delete() throws IOException { + } + + public Object set(ATTRIBUTES iAttribute, Object iValue) throws IOException { + return null; + } + + @Override + public String encryption() { + throw new UnsupportedOperationException("encryption"); + } + + public void truncate() throws IOException { + } + + @Override + public void compact() throws IOException { + } + + public OPhysicalPosition getPhysicalPosition(OPhysicalPosition iPPosition) throws IOException { + return null; + } + + public long getEntries() { + return 0; + } + + @Override + public long getTombstonesCount() { + throw new UnsupportedOperationException("getTombstonesCount()"); + } + + @Override + public long getFirstPosition() { + return 0; + } + + @Override + public long getLastPosition() { + return 0; + } + + @Override + public long getNextPosition() throws IOException { + return 0; + } + + @Override + public String getFileName() { + throw new UnsupportedOperationException("getFileName()"); + } + + public int getId() { + return id; + } + + public void synch() throws IOException { + } + + public String getName() { + return name; + } + + public long getRecordsSize() { + throw new UnsupportedOperationException("getRecordsSize()"); + } + + public boolean isHashBased() { + return false; + } + + @Override + public boolean isSystemCluster() { + return false; + } + + public OClusterEntryIterator absoluteIterator() { + throw new UnsupportedOperationException("getRecordsSize()"); + } + + @Override + public OPhysicalPosition[] higherPositions(OPhysicalPosition position) { + throw new UnsupportedOperationException("higherPositions()"); + } + + @Override + public OPhysicalPosition[] lowerPositions(OPhysicalPosition position) { + throw new UnsupportedOperationException("lowerPositions()"); + } + + @Override + public OPhysicalPosition[] ceilingPositions(OPhysicalPosition position) throws IOException { + throw new UnsupportedOperationException("ceilingPositions()"); + } + + @Override + public OPhysicalPosition[] floorPositions(OPhysicalPosition position) throws IOException { + throw new UnsupportedOperationException("floorPositions()"); + } + + @Override + public float recordGrowFactor() { + throw new UnsupportedOperationException("recordGrowFactor()"); + } + + @Override + public float recordOverflowGrowFactor() { + throw new UnsupportedOperationException("recordOverflowGrowFactor()"); + } + + @Override + public String compression() { + throw new UnsupportedOperationException("compression()"); + } + + @Override + public boolean hideRecord(long position) { + throw new UnsupportedOperationException("Operation is not supported for given cluster implementation"); + } + + @Override + public ORecordConflictStrategy getRecordConflictStrategy() { + return null; + } + + @Override + public void acquireAtomicExclusiveLock() { + throw new UnsupportedOperationException("remote cluster doesn't support atomic locking"); + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OCollectionNetworkSerializer.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OCollectionNetworkSerializer.java new file mode 100644 index 00000000000..995a142ed2f --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OCollectionNetworkSerializer.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary; + +import java.io.IOException; + +public class OCollectionNetworkSerializer { + public static final OCollectionNetworkSerializer INSTANCE = new OCollectionNetworkSerializer(); + + public OCollectionNetworkSerializer() { + } + + public OBonsaiCollectionPointer readCollectionPointer(final OChannelBinary client) throws IOException { + final long fileId = client.readLong(); + final OBonsaiBucketPointer rootPointer = readBonsaiBucketPointer(client); + return new OBonsaiCollectionPointer(fileId, rootPointer); + } + + private OBonsaiBucketPointer readBonsaiBucketPointer(OChannelBinary client) throws IOException { + long pageIndex = client.readLong(); + int pageOffset = client.readInt(); + return new OBonsaiBucketPointer(pageIndex, pageOffset); + } + + public void writeCollectionPointer(OChannelBinary client, OBonsaiCollectionPointer treePointer) throws IOException { + client.writeLong(treePointer.getFileId()); + client.writeLong(treePointer.getRootPointer().getPageIndex()); + client.writeInt(treePointer.getRootPointer().getPageOffset()); + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/ODatabaseImportRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/ODatabaseImportRemote.java new file mode 100644 index 00000000000..94d93fc9045 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/ODatabaseImportRemote.java @@ -0,0 +1,55 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.tool.ODatabaseImpExpAbstract; +import com.orientechnologies.orient.core.db.tool.ODatabaseImportException; +import com.orientechnologies.orient.core.db.tool.ODatabaseTool; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +/** + * Created by tglman on 19/07/16. + */ +public class ODatabaseImportRemote extends ODatabaseImpExpAbstract { + + private String options; + + public ODatabaseImportRemote(ODatabaseDocumentInternal iDatabase, String iFileName, OCommandOutputListener iListener) { + super(iDatabase, iFileName, iListener); + } + + @Override + public void run() { + try { + importDatabase(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public ODatabaseTool setOptions(String iOptions) { + this.options = iOptions; + return super.setOptions(iOptions); + } + + public void importDatabase() throws ODatabaseImportException { + OStorageRemote storage = (OStorageRemote) ((ODatabaseDocumentInternal) getDatabase()).getStorage(); + File file = new File(getFileName()); + try { + storage.importDatabase(options, new FileInputStream(file), file.getName(), getListener()); + } catch (FileNotFoundException e) { + throw OException.wrapException(new ODatabaseImportException("Error importing the database"), e); + } + } + + public void close() { + + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OEngineRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OEngineRemote.java new file mode 100755 index 00000000000..fa84f1c6c82 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OEngineRemote.java @@ -0,0 +1,89 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.engine.OEngineAbstract; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Map; + +/** + * Remote engine implementation. + * + * @author Luca Garulli + */ +public class OEngineRemote extends OEngineAbstract { + public static final String NAME = "remote"; + public static final String PREFIX = NAME + ":"; + protected volatile ORemoteConnectionManager connectionManager; + + public OEngineRemote() { + } + + public OStorage createStorage(final String iURL, final Map iConfiguration) { + try { + return new OStorageRemote(null, iURL, "rw"); + } catch (Exception e) { + final String message = "Error on opening database: " + iURL; + OLogManager.instance().error(this, message, e); + throw OException.wrapException(new ODatabaseException(message), e); + } + } + + @Override + public void removeStorage(final OStorage iStorage) { + } + + @Override + public void startup() { + super.startup(); + + connectionManager = new ORemoteConnectionManager(OGlobalConfiguration.NETWORK_LOCK_TIMEOUT.getValueAsLong()); + } + + @Override + public void shutdown() { + try { + //We call shutdown in case of failed startup, the connection manager may have not been initialized + if (connectionManager != null) + connectionManager.close(); + } finally { + super.shutdown(); + } + } + + @Override + public String getNameFromPath(String dbPath) { + return dbPath; + } + + public ORemoteConnectionManager getConnectionManager() { + return connectionManager; + } + + public String getName() { + return NAME; + } + +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionManager.java b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionManager.java new file mode 100755 index 00000000000..e5df99e7ffa --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionManager.java @@ -0,0 +1,242 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import static com.orientechnologies.orient.core.config.OGlobalConfiguration.*; + +/** + * Manages network connections against OrientDB servers. All the connection pools are managed in a Map, but in the future + * we could have a unique pool per sever and manage database connections over the protocol. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ORemoteConnectionManager { + public static final String PARAM_MAX_POOL = "maxpool"; + + protected final ConcurrentMap connections; + protected final long timeout; + protected final long idleTimeout; + private final TimerTask idleTask; + + public ORemoteConnectionManager(final OContextConfiguration clientConfiguration, Timer timer) { + connections = new ConcurrentHashMap(); + timeout = clientConfiguration.getValueAsLong(NETWORK_LOCK_TIMEOUT); + int idleSecs = clientConfiguration.getValueAsInteger(CLIENT_CHANNEL_IDLE_TIMEOUT); + this.idleTimeout = TimeUnit.MILLISECONDS.convert(idleSecs, TimeUnit.SECONDS); + if (clientConfiguration.getValueAsBoolean(CLIENT_CHANNEL_IDLE_CLOSE)) { + idleTask = new TimerTask() { + @Override + public void run() { + checkIdle(); + } + }; + timer.schedule(this.idleTask, this.idleTimeout / 3); + } else { + idleTask = null; + } + } + + public void close() { + for (Map.Entry entry : connections.entrySet()) { + closePool(entry.getValue()); + } + + connections.clear(); + if (idleTask != null) { + idleTask.cancel(); + } + } + + public OChannelBinaryAsynchClient acquire(String iServerURL, final OContextConfiguration clientConfiguration, + final Map iConfiguration, final OStorageRemoteAsynchEventListener iListener) { + if (iServerURL.startsWith(OEngineRemote.PREFIX)) + iServerURL = iServerURL.substring(OEngineRemote.PREFIX.length()); + + if (iServerURL.endsWith("/")) + iServerURL = iServerURL.substring(0, iServerURL.length() - 1); + + long localTimeout = timeout; + + ORemoteConnectionPool pool = connections.get(iServerURL); + if (pool == null) { + int maxPool = OGlobalConfiguration.CLIENT_CHANNEL_MAX_POOL.getValueAsInteger(); + + if (iConfiguration != null && iConfiguration.size() > 0) { + if (iConfiguration.containsKey(PARAM_MAX_POOL)) + maxPool = Integer.parseInt(iConfiguration.get(PARAM_MAX_POOL).toString()); + if (iConfiguration.containsKey(PARAM_MAX_POOL)) + maxPool = Integer.parseInt(iConfiguration.get(PARAM_MAX_POOL).toString()); + } + + if (clientConfiguration != null) { + final Object max = clientConfiguration.getValue(OGlobalConfiguration.CLIENT_CHANNEL_MAX_POOL); + if (max != null) + maxPool = Integer.parseInt(max.toString()); + + final Object netLockTimeout = clientConfiguration.getValue(NETWORK_LOCK_TIMEOUT); + if (netLockTimeout != null) + localTimeout = Integer.parseInt(netLockTimeout.toString()); + } + + pool = new ORemoteConnectionPool(maxPool, iListener != null); + final ORemoteConnectionPool prev = connections.putIfAbsent(iServerURL, pool); + if (prev != null) { + // ALREADY PRESENT, DESTROY IT AND GET THE ALREADY EXISTENT OBJ + pool.getPool().close(); + pool = prev; + } + } + + try { + // RETURN THE RESOURCE + OChannelBinaryAsynchClient ret = pool.acquire(iServerURL, localTimeout, clientConfiguration, iConfiguration, iListener); + ret.markInUse(); + return ret; + + } catch (RuntimeException e) { + // ERROR ON RETRIEVING THE INSTANCE FROM THE POOL + throw e; + } catch (Exception e) { + // ERROR ON RETRIEVING THE INSTANCE FROM THE POOL + OLogManager.instance().debug(this, "Error on retrieving the connection from pool: " + iServerURL, e); + } + return null; + } + + public void release(final OChannelBinaryAsynchClient conn) { + if (conn == null) + return; + + conn.markReturned(); + final ORemoteConnectionPool pool = connections.get(conn.getServerURL()); + if (pool != null) { + if (!conn.isConnected()) { + OLogManager.instance().debug(this, "Network connection pool is receiving a closed connection to reuse: discard it"); + remove(conn); + } else { + pool.getPool().returnResource(conn); + } + } + } + + public void remove(final OChannelBinaryAsynchClient conn) { + if (conn == null) + return; + + final ORemoteConnectionPool pool = connections.get(conn.getServerURL()); + if (pool == null) { + OLogManager.instance() + .debug(this, "Connection '%s' cannot be released because the pool doesn't exist anymore", conn.getServerURL()); + return; + } + + pool.getPool().remove(conn); + + try { + conn.unlock(); + } catch (Exception e) { + OLogManager.instance().debug(this, "Cannot unlock connection lock", e); + } + + try { + conn.close(); + } catch (Exception e) { + OLogManager.instance().debug(this, "Cannot close connection", e); + } + + } + + public Set getURLs() { + return connections.keySet(); + } + + public int getMaxResources(final String url) { + final ORemoteConnectionPool pool = connections.get(url); + if (pool == null) + return 0; + + return pool.getPool().getMaxResources(); + } + + public int getAvailableConnections(final String url) { + final ORemoteConnectionPool pool = connections.get(url); + if (pool == null) + return 0; + + return pool.getPool().getAvailableResources(); + } + + public int getReusableConnections(final String url) { + final ORemoteConnectionPool pool = connections.get(url); + if (pool == null) + return 0; + + return pool.getPool().getInPoolResources(); + } + + public int getCreatedInstancesInPool(final String url) { + final ORemoteConnectionPool pool = connections.get(url); + if (pool == null) + return 0; + + return pool.getPool().getCreatedInstances(); + } + + public void closePool(final String url) { + final ORemoteConnectionPool pool = connections.remove(url); + if (pool == null) + return; + + closePool(pool); + } + + protected void closePool(ORemoteConnectionPool pool) { + final List conns = new ArrayList(pool.getPool().getAllResources()); + for (OChannelBinaryAsynchClient c : conns) + try { + // Unregister the listener that make the connection return to the closing pool. + c.close(); + } catch (Exception e) { + OLogManager.instance().debug(this, "Cannot close binary channel", e); + } + pool.getPool().close(); + } + + public ORemoteConnectionPool getPool(String url) { + return connections.get(url); + } + + public void checkIdle() { + for (Map.Entry entry : connections.entrySet()) { + entry.getValue().checkIdle(idleTimeout); + } + } + +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPool.java b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPool.java new file mode 100644 index 00000000000..5c971cc6a49 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPool.java @@ -0,0 +1,112 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.concur.resource.OResourcePool; +import com.orientechnologies.common.concur.resource.OResourcePoolListener; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.enterprise.channel.OChannel; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelListener; + +import java.util.Map; + +/** + * Created by tglman on 01/10/15. + */ +public class ORemoteConnectionPool implements OResourcePoolListener { + + private OResourcePool pool; + private ORemoteConnectionPushListener listener; + + public ORemoteConnectionPool(int iMaxResources, final boolean createAsyncListener) { + pool = new OResourcePool(iMaxResources, this); + listener = createAsyncListener ? new ORemoteConnectionPushListener() : null; + } + + protected OChannelBinaryAsynchClient createNetworkConnection(String iServerURL, final OContextConfiguration clientConfiguration, + Map iAdditionalArg) throws OIOException { + if (iServerURL == null) + throw new IllegalArgumentException("server url is null"); + + // TRY WITH CURRENT URL IF ANY + try { + OLogManager.instance().debug(this, "Trying to connect to the remote host %s...", iServerURL); + + final String serverURL; + final String databaseName; + + if (iServerURL.startsWith(OEngineRemote.PREFIX)) + iServerURL = iServerURL.substring(OEngineRemote.PREFIX.length()); + + int sepPos = iServerURL.indexOf("/"); + if (sepPos > -1) { + // REMOVE DATABASE NAME IF ANY + serverURL = iServerURL.substring(0, sepPos); + databaseName = iServerURL.substring(sepPos + 1); + } else { + serverURL = iServerURL; + databaseName = null; + } + + sepPos = serverURL.indexOf(":"); + final String remoteHost = serverURL.substring(0, sepPos); + final int remotePort = Integer.parseInt(serverURL.substring(sepPos + 1)); + + final OChannelBinaryAsynchClient ch = new OChannelBinaryAsynchClient(remoteHost, remotePort, databaseName, + clientConfiguration, OChannelBinaryProtocol.CURRENT_PROTOCOL_VERSION, listener); + + return ch; + + } catch (OIOException e) { + // RE-THROW IT + throw e; + } catch (Exception e) { + OLogManager.instance().debug(this, "Error on connecting to %s", e, iServerURL); + throw OException.wrapException(new OIOException("Error on connecting to " + iServerURL), e); + } + } + + @Override + public OChannelBinaryAsynchClient createNewResource(final String iKey, final Object... iAdditionalArgs) { + return createNetworkConnection(iKey, (OContextConfiguration) iAdditionalArgs[0], (Map) iAdditionalArgs[1]); + } + + @Override + public boolean reuseResource(final String iKey, final Object[] iAdditionalArgs, final OChannelBinaryAsynchClient iValue) { + final boolean canReuse = iValue.isConnected(); + if (!canReuse) + // CANNOT REUSE: CLOSE IT PROPERLY + try { + iValue.close(); + } catch (Exception e) { + OLogManager.instance().debug(this, "Error on closing socket connection", e); + } + iValue.markInUse(); + return canReuse; + } + + public OResourcePool getPool() { + return pool; + } + + public OChannelBinaryAsynchClient acquire(final String iServerURL, final long timeout, + final OContextConfiguration clientConfiguration, final Map iConfiguration, + final OStorageRemoteAsynchEventListener iListener) { + final OChannelBinaryAsynchClient ret = pool + .getResource(iServerURL, timeout, clientConfiguration, iConfiguration, iListener != null); + if (listener != null && iListener != null) + listener.addListener(this, ret, iListener); + return ret; + } + + public void checkIdle(long timeout) { + for (OChannelBinaryAsynchClient resource : pool.getResources()) { + if (!resource.isInUse() && resource.getLastUse() + timeout < System.currentTimeMillis()) { + resource.close(); + } + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListener.java b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListener.java new file mode 100644 index 00000000000..def1ee25702 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListener.java @@ -0,0 +1,58 @@ +package com.orientechnologies.orient.client.remote; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.enterprise.channel.OChannel; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelListener; +import com.orientechnologies.orient.enterprise.channel.binary.ORemoteServerEventListener; + +/** + * Created by tglman on 01/10/15. + */ +public class ORemoteConnectionPushListener implements ORemoteServerEventListener { + + private Set listeners = Collections + .synchronizedSet(Collections.newSetFromMap(new WeakHashMap())); + private ConcurrentMap> conns = new ConcurrentHashMap>(); + + public void addListener(final ORemoteConnectionPool pool, final OChannelBinaryAsynchClient connection, final OStorageRemoteAsynchEventListener listener) { + this.listeners.add(listener); + Set ans = conns.get(listener); + if (ans == null) { + ans =Collections.synchronizedSet(new HashSet()); + Set putRet = conns.putIfAbsent(listener, ans); + if(putRet != null) + ans = putRet; + } + if (!ans.contains(connection)) { + ans.add(connection); + connection.registerListener(new OChannelListener() { + @Override + public void onChannelClose(OChannel iChannel) { + Set all = conns.get(listener); + all.remove(iChannel); + if (all.isEmpty()){ + listener.onEndUsedConnections(pool); + } + connection.unregisterListener(this); + } + }); + } + } + + public void removeListener(ORemoteServerEventListener listener) { + this.listeners.remove(listener); + } + + public void onRequest(final byte iRequestCode, Object obj) { + for (ORemoteServerEventListener listener : listeners) { + listener.onRequest(iRequestCode, obj); + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeBonsaiRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeBonsaiRemote.java new file mode 100644 index 00000000000..6eabb9b180b --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeBonsaiRemote.java @@ -0,0 +1,316 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeRidBag; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link OSBTreeBonsai} for remote storage. + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeBonsaiRemote implements OSBTreeBonsai { + private final OBonsaiCollectionPointer treePointer; + private final OBinarySerializer keySerializer; + private final OBinarySerializer valueSerializer; + + public OSBTreeBonsaiRemote(OBonsaiCollectionPointer treePointer, OBinarySerializer keySerializer, + OBinarySerializer valueSerializer) { + this.treePointer = treePointer; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + @Override + public long getFileId() { + return treePointer.getFileId(); + } + + @Override + public OBonsaiBucketPointer getRootBucketPointer() { + return treePointer.getRootPointer(); + } + + @Override + public OBonsaiCollectionPointer getCollectionPointer() { + return treePointer; + } + + @Override + public V get(K key) { + final OStorageRemote storage = (OStorageRemote) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + + final byte[] keyStream = new byte[keySerializer.getObjectSize(key)]; + keySerializer.serialize(key, keyStream, 0); + return storage.networkOperation(new OStorageRemoteOperation() { + @Override + public V execute(final OChannelBinaryAsynchClient client, OStorageRemoteSession session) throws IOException { + storage.beginRequest(client,OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET, session); + OCollectionNetworkSerializer.INSTANCE.writeCollectionPointer(client, getCollectionPointer()); + client.writeBytes(keyStream); + + storage.endRequest(client); + + byte[] stream; + try { + storage.beginResponse(client, session); + stream = client.readBytes(); + } finally { + storage.endResponse(client); + } + + final byte serializerId = OByteSerializer.INSTANCE.deserializeLiteral(stream, 0); + final OBinarySerializer serializer = (OBinarySerializer) OBinarySerializerFactory.getInstance().getObjectSerializer( + serializerId); + return serializer.deserialize(stream, OByteSerializer.BYTE_SIZE); + } + },"Cannot get by key from sb-tree bonsai"); + } + + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public V remove(K key) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void delete() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public long size() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Collection getValuesMinor(K key, boolean inclusive, int maxValuesToFetch) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void loadEntriesMinor(K key, boolean inclusive, RangeResultListener listener) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Collection getValuesMajor(K key, boolean inclusive, int maxValuesToFetch) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void loadEntriesMajor(K key, boolean inclusive, boolean ascSortOrder, RangeResultListener listener) { + if (!ascSortOrder) + throw new IllegalStateException("Descending sort order is not supported"); + + List> entries = fetchEntriesMajor(key, inclusive); + + while (pushEntriesToListener(listener, entries)) { + final K nextKey = entries.get(entries.size() - 1).getKey(); + entries = fetchEntriesMajor(nextKey, false); + } + } + + private boolean pushEntriesToListener(RangeResultListener listener, List> entries) { + boolean more = false; + for (Map.Entry entry : entries) { + more = listener.addResult(entry); + + if (!more) + return false; + } + return more; + } + + private List> fetchEntriesMajor(final K key,final boolean inclusive) { + final byte[] keyStream = new byte[keySerializer.getObjectSize(key)]; + keySerializer.serialize(key, keyStream, 0); + final OStorageRemote storage = (OStorageRemote) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + return storage.networkOperation(new OStorageRemoteOperation>>() { + @Override + public List> execute(final OChannelBinaryAsynchClient client, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(client, OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET_ENTRIES_MAJOR, session); + OCollectionNetworkSerializer.INSTANCE.writeCollectionPointer(client, getCollectionPointer()); + client.writeBytes(keyStream); + client.writeBoolean(inclusive); + + if (client.getSrvProtocolVersion() >= 21) + client.writeInt(128); + }finally { + storage.endRequest(client); + } + List> list = null; + try { + storage.beginResponse(client, session); + byte[] stream = client.readBytes(); + int offset = 0; + final int count = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, 0); + offset += OIntegerSerializer.INT_SIZE; + list = new ArrayList>(count); + for (int i = 0; i < count; i++) { + final K resultKey = keySerializer.deserialize(stream, offset); + offset += keySerializer.getObjectSize(stream, offset); + final V resultValue = valueSerializer.deserialize(stream, offset); + offset += valueSerializer.getObjectSize(stream, offset); + list.add(new TreeEntry(resultKey, resultValue)); + } + } finally { + storage.endResponse(client); + } + + return list; + } + },"Cannot get first key from sb-tree bonsai"); + } + + @Override + public Collection getValuesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, int maxValuesToFetch) { + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public K firstKey() { + final OStorageRemote storage = (OStorageRemote) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + return storage.networkOperation(new OStorageRemoteOperation() { + @Override + public K execute(final OChannelBinaryAsynchClient client, OStorageRemoteSession session) throws IOException { + storage.beginRequest(client,OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_FIRST_KEY, session); + OCollectionNetworkSerializer.INSTANCE.writeCollectionPointer(client, getCollectionPointer()); + storage.endRequest(client); + byte[] stream; + try { + storage.beginResponse(client, session); + stream = client.readBytes(); + } finally { + storage.endResponse(client); + } + + final byte serializerId = OByteSerializer.INSTANCE.deserializeLiteral(stream, 0); + final OBinarySerializer serializer = (OBinarySerializer) OBinarySerializerFactory.getInstance().getObjectSerializer( + serializerId); + return serializer.deserialize(stream, OByteSerializer.BYTE_SIZE); + } + },"Cannot get first key from sb-tree bonsai"); + } + + @Override + public K lastKey() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void loadEntriesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, RangeResultListener listener) { + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public int getRealBagSize(final Map changes) { + final OStorageRemote storage = (OStorageRemote) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + return storage.networkOperation(new OStorageRemoteOperation() { + @Override + public Integer execute(OChannelBinaryAsynchClient client, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(client, OChannelBinaryProtocol.REQUEST_RIDBAG_GET_SIZE, session); + OCollectionNetworkSerializer.INSTANCE.writeCollectionPointer(client, getCollectionPointer()); + + final OSBTreeRidBag.ChangeSerializationHelper changeSerializer = OSBTreeRidBag.ChangeSerializationHelper.INSTANCE; + final byte[] stream = new byte[OIntegerSerializer.INT_SIZE + changeSerializer.getChangesSerializedSize(changes.size())]; + changeSerializer.serializeChanges(changes, keySerializer, stream, 0); + + client.writeBytes(stream); + } finally { + storage.endRequest(client); + } + int result; + try { + storage.beginResponse(client, session); + result = client.readInt(); + } finally { + storage.endResponse(client); + } + return result; + } + }, "Cannot get by real bag size sb-tree bonsai"); + } + + @Override + public OBinarySerializer getKeySerializer() { + return keySerializer; + } + + @Override + public OBinarySerializer getValueSerializer() { + return valueSerializer; + } + + class TreeEntry implements Map.Entry { + private final EK key; + private final EV value; + + TreeEntry(EK key, EV value) { + this.key = key; + this.value = value; + } + + @Override + public EK getKey() { + return key; + } + + @Override + public EV getValue() { + return value; + } + + @Override + public EV setValue(EV value) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemote.java new file mode 100755 index 00000000000..b58f5ad6d39 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemote.java @@ -0,0 +1,164 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeCollectionManagerAbstract; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeCollectionManagerRemote extends OSBTreeCollectionManagerAbstract { + + private final OCollectionNetworkSerializer networkSerializer; + private boolean remoteCreationAllowed = false; + + private volatile ThreadLocal>> pendingCollections = new PendingCollectionsThreadLocal(); + + public OSBTreeCollectionManagerRemote(OStorage storage) { + super(storage); + networkSerializer = new OCollectionNetworkSerializer(); + } + + public OSBTreeCollectionManagerRemote(OStorage storage, OCollectionNetworkSerializer networkSerializer) { + super(storage); + this.networkSerializer = networkSerializer; + } + + @Override + public void onShutdown() { + pendingCollections = null; + super.onShutdown(); + } + + @Override + public void onStartup() { + super.onStartup(); + if (pendingCollections == null) + pendingCollections = new PendingCollectionsThreadLocal(); + } + + @Override + protected OSBTreeBonsaiRemote createTree(final int clusterId) { + if (remoteCreationAllowed) { + final OStorageRemote storage = (OStorageRemote) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + return storage.networkOperation(new OStorageRemoteOperation>() { + @Override + public OSBTreeBonsaiRemote execute(final OChannelBinaryAsynchClient client, + OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(client, OChannelBinaryProtocol.REQUEST_CREATE_SBTREE_BONSAI, session); + client.writeInt(clusterId); + } finally { + storage.endRequest(client); + } + OBonsaiCollectionPointer pointer; + try { + storage.beginResponse(client, session); + pointer = networkSerializer.readCollectionPointer(client); + } finally { + storage.endResponse(client); + } + + OBinarySerializer keySerializer = OLinkSerializer.INSTANCE; + OBinarySerializer valueSerializer = OIntegerSerializer.INSTANCE; + + return new OSBTreeBonsaiRemote(pointer, keySerializer, valueSerializer); + } + }, "Cannot create sb-tree bonsai"); + } else { + throw new UnsupportedOperationException("Creation of SB-Tree from remote storage is not allowed"); + } + } + + @Override + protected OSBTreeBonsai loadTree(OBonsaiCollectionPointer collectionPointer) { + OBinarySerializer keySerializer = OLinkSerializer.INSTANCE; + OBinarySerializer valueSerializer = OIntegerSerializer.INSTANCE; + + return new OSBTreeBonsaiRemote(collectionPointer, keySerializer, valueSerializer); + } + + @Override + public UUID listenForChanges(ORidBag collection) { + UUID id = collection.getTemporaryId(); + if (id == null) + id = UUID.randomUUID(); + + pendingCollections.get().put(id, new WeakReference(collection)); + + return id; + } + + @Override + public void updateCollectionPointer(UUID uuid, OBonsaiCollectionPointer pointer) { + final WeakReference reference = pendingCollections.get().get(uuid); + if (reference == null) { + OLogManager.instance().warn(this, "Update of collection pointer is received but collection is not registered"); + return; + } + + final ORidBag collection = reference.get(); + + if (collection != null) { + collection.notifySaved(pointer); + } + } + + @Override + public void clearPendingCollections() { + pendingCollections.get().clear(); + } + + @Override + public Map changedIds() { + throw new UnsupportedOperationException(); + } + + @Override + public void clearChangedIds() { + throw new UnsupportedOperationException(); + } + + private static class PendingCollectionsThreadLocal extends ThreadLocal>> { + @Override + protected Map> initialValue() { + return new HashMap>(); + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OServerAdmin.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OServerAdmin.java new file mode 100755 index 00000000000..a5b8f575f8b --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OServerAdmin.java @@ -0,0 +1,709 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.OCredentialInterceptor; +import com.orientechnologies.orient.core.security.OSecurityManager; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Remote administration class of OrientDB Server instances. + */ +public class OServerAdmin { + protected OStorageRemote storage; + protected OStorageRemoteSession session = new OStorageRemoteSession(-1); + protected String clientType = OStorageRemote.DRIVER_NAME; + protected boolean collectStats = true; + + /** + * Creates the object passing a remote URL to connect. sessionToken + * + * @param iURL URL to connect. It supports only the "remote" storage type. + * @throws IOException + */ + public OServerAdmin(String iURL) throws IOException { + if (iURL.startsWith(OEngineRemote.NAME)) + iURL = iURL.substring(OEngineRemote.NAME.length() + 1); + + if (!iURL.contains("/")) + iURL += "/"; + + storage = new OStorageRemote(null, iURL, "", OStorage.STATUS.OPEN, true) { + @Override + protected OStorageRemoteSession getCurrentSession() { + return session; + } + }; + } + + /** + * Creates the object starting from an existent remote storage. + * + * @param iStorage + */ + public OServerAdmin(final OStorageRemote iStorage) { + storage = iStorage; + } + + /** + * Connects to a remote server. + * + * @param iUserName Server's user name + * @param iUserPassword Server's password for the user name used + * @return The instance itself. Useful to execute method in chain + * @throws IOException + */ + public synchronized OServerAdmin connect(final String iUserName, final String iUserPassword) throws IOException { + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + OStorageRemoteNodeSession nodeSession = storage.getCurrentSession().getOrCreateServerSession(network.getServerURL()); + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_CONNECT, session); + + storage.sendClientInfo(network, clientType, false, collectStats); + + String username = iUserName; + String password = iUserPassword; + + OCredentialInterceptor ci = OSecurityManager.instance().newCredentialInterceptor(); + + if (ci != null) { + ci.intercept(storage.getURL(), iUserName, iUserPassword); + username = ci.getUsername(); + password = ci.getPassword(); + } + + network.writeString(username); + network.writeString(password); + } finally { + storage.endRequest(network); + } + + try { + network.beginResponse(nodeSession.getSessionId(), false); + int sessionId = network.readInt(); + byte[] sessionToken = network.readBytes(); + if (sessionToken.length == 0) { + sessionToken = null; + } + nodeSession.setSession(sessionId, sessionToken); + } finally { + storage.endResponse(network); + } + + return null; + } + }, "Cannot connect to the remote server/database '" + storage.getURL() + "'"); + return this; + } + + /** + * Returns the list of databases on the connected remote server. + * + * @throws IOException + */ + @SuppressWarnings("unchecked") + public synchronized Map listDatabases() throws IOException { + return networkAdminOperation(new OStorageRemoteOperation>() { + @Override + public Map execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_LIST, session); + } finally { + storage.endRequest(network); + } + + final ODocument result = new ODocument(); + try { + storage.beginResponse(network, session); + result.fromStream(network.readBytes()); + } finally { + storage.endResponse(network); + } + + return (Map) result.field("databases"); + } + }, "Cannot retrieve the configuration list"); + + } + + /** + * Returns the server information in form of document. + * + * @throws IOException + */ + @SuppressWarnings("unchecked") + public synchronized ODocument getServerInfo() throws IOException { + return networkAdminOperation(new OStorageRemoteOperation() { + @Override + public ODocument execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_SERVER_INFO, session); + } finally { + storage.endRequest(network); + } + + final ODocument result = new ODocument(); + try { + storage.beginResponse(network, session); + result.fromJSON(network.readString()); + } finally { + storage.endResponse(network); + } + return result; + } + }, "Cannot retrieve server information"); + } + + public int getSessionId() { + return session.getSessionId(); + } + + /** + * Deprecated. Use the {@link #createDatabase(String, String)} instead. + */ + @Deprecated + public synchronized OServerAdmin createDatabase(final String iStorageMode) throws IOException { + return createDatabase("document", iStorageMode); + } + + /** + * Creates a database in a remote server. + * + * @param iDatabaseType 'document' or 'graph' + * @param iStorageMode local or memory + * @return The instance itself. Useful to execute method in chain + * @throws IOException + */ + public synchronized OServerAdmin createDatabase(final String iDatabaseType, String iStorageMode) throws IOException { + return createDatabase(storage.getName(), iDatabaseType, iStorageMode); + } + + public synchronized String getStorageName() { + return storage.getName(); + } + + public synchronized OServerAdmin createDatabase(final String iDatabaseName, final String iDatabaseType, final String iStorageMode) + throws IOException { + return createDatabase(iDatabaseName, iDatabaseType, iStorageMode, null); + } + + /** + * Creates a database in a remote server. + * + * @param iDatabaseName The database name + * @param iDatabaseType 'document' or 'graph' + * @param iStorageMode local or memory + * @param backupPath path to incremental backup which will be used to create database (optional) + * @return The instance itself. Useful to execute method in chain + * @throws IOException + */ + public synchronized OServerAdmin createDatabase(final String iDatabaseName, final String iDatabaseType, final String iStorageMode, + final String backupPath) throws IOException { + + if (iDatabaseName == null || iDatabaseName.length() <= 0) { + final String message = "Cannot create unnamed remote storage. Check your syntax"; + OLogManager.instance().error(this, message); + throw new OStorageException(message); + } else { + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + String storageMode; + if (iStorageMode == null) + storageMode = "csv"; + else + storageMode = iStorageMode; + + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_CREATE, session); + network.writeString(iDatabaseName); + if (network.getSrvProtocolVersion() >= 8) + network.writeString(iDatabaseType); + network.writeString(storageMode); + + if (network.getSrvProtocolVersion() > 35) + network.writeString(backupPath); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return null; + } + }, "Cannot create the remote storage: " + storage.getName()); + + } + + return this; + } + + /** + * Checks if a database exists in the remote server. + * + * @return true if exists, otherwise false + */ + public synchronized boolean existsDatabase() throws IOException { + return existsDatabase(null); + } + + /** + * Checks if a database exists in the remote server. + * + * @param iDatabaseName The database name + * @param storageType Storage type between "plocal" or "memory". + * @return true if exists, otherwise false + * @throws IOException + */ + public synchronized boolean existsDatabase(final String iDatabaseName, final String storageType) throws IOException { + + return networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Boolean execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_EXIST, session); + network.writeString(iDatabaseName); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + try { + storage.beginResponse(network, session); + return network.readByte() == 1; + } finally { + storage.endResponse(network); + } + } + }, "Error on checking existence of the remote storage: " + storage.getName()); + + } + + /** + * Checks if a database exists in the remote server. + * + * @param storageType Storage type between "plocal" or "memory". + * @return true if exists, otherwise false + * @throws IOException + */ + public synchronized boolean existsDatabase(final String storageType) throws IOException { + return existsDatabase(storage.getName(), storageType); + } + + /** + * Deprecated. Use dropDatabase() instead. + * + * @param storageType Storage type between "plocal" or "memory". + * @return The instance itself. Useful to execute method in chain + * @throws IOException + * @see #dropDatabase(String) + */ + @Deprecated + public OServerAdmin deleteDatabase(final String storageType) throws IOException { + return dropDatabase(storageType); + } + + /** + * Drops a database from a remote server instance. + * + * @param iDatabaseName The database name + * @param storageType Storage type between "plocal" or "memory". + * @return The instance itself. Useful to execute method in chain + * @throws IOException + */ + public synchronized OServerAdmin dropDatabase(final String iDatabaseName, final String storageType) throws IOException { + + boolean retry = true; + while (retry) { + retry = networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Boolean execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_DROP, session); + network.writeString(iDatabaseName); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return false; + } catch (OModificationOperationProhibitedException oope) { + return handleDBFreeze(); + } + + } + }, "Cannot delete the remote storage: " + storage.getName()); + } + + final Set underlyingStorages = new HashSet(); + + for (OStorage s : Orient.instance().getStorages()) { + if (s.getType().equals(storage.getType()) && s.getName().equals(storage.getName())) { + underlyingStorages.add(s.getUnderlying()); + } + } + + for (OStorage s : underlyingStorages) { + s.close(true, true); + } + + ODatabaseRecordThreadLocal.INSTANCE.remove(); + + return this; + } + + /** + * Drops a database from a remote server instance. + * + * @param storageType Storage type between "plocal" or "memory". + * @return The instance itself. Useful to execute method in chain + * @throws IOException + */ + public synchronized OServerAdmin dropDatabase(final String storageType) throws IOException { + return dropDatabase(storage.getName(), storageType); + } + + /** + * Freezes the database by locking it in exclusive mode. + * + * @param storageType Storage type between "plocal" or "memory". + * @return + * @throws IOException + * @see #releaseDatabase(String) + */ + public synchronized OServerAdmin freezeDatabase(final String storageType) throws IOException { + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_FREEZE, session); + network.writeString(storage.getName()); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return null; + } + }, "Cannot freeze the remote storage: " + storage.getName()); + + return this; + } + + /** + * Releases a frozen database. + * + * @param storageType Storage type between "plocal" or "memory". + * @return + * @throws IOException + * @see #freezeDatabase(String) + */ + public synchronized OServerAdmin releaseDatabase(final String storageType) throws IOException { + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_RELEASE, session); + network.writeString(storage.getName()); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return null; + } + }, "Cannot release the remote storage: " + storage.getName()); + + return this; + } + + /** + * Freezes a cluster by locking it in exclusive mode. + * + * @param clusterId Id of cluster to freeze + * @param storageType Storage type between "plocal" or "memory". + * @return + * @throws IOException + * @see #releaseCluster(int, String) + */ + + public synchronized OServerAdmin freezeCluster(final int clusterId, final String storageType) throws IOException { + + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_FREEZE, session); + network.writeString(storage.getName()); + network.writeShort((short) clusterId); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return null; + } + }, "Cannot freeze the remote cluster " + clusterId + " on storage: " + storage.getName()); + + return this; + } + + /** + * Releases a frozen cluster. + * + * @param clusterId Id of cluster to freeze + * @param storageType Storage type between "plocal" or "memory". + * @return + * @throws IOException + * @see #freezeCluster(int, String) + */ + public synchronized OServerAdmin releaseCluster(final int clusterId, final String storageType) throws IOException { + + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_RELEASE, session); + network.writeString(storage.getName()); + network.writeShort((short) clusterId); + network.writeString(storageType); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + return null; + } + }, "Cannot release the remote cluster " + clusterId + " on storage: " + storage.getName()); + + return this; + } + + /** + * Gets the cluster status. + * + * @return the JSON containing the current cluster structure + */ + public ODocument clusterStatus() { + final ODocument response = sendRequest(OChannelBinaryProtocol.REQUEST_CLUSTER, new ODocument().field("operation", "status"), + "Cluster status"); + + OLogManager.instance().debug(this, "Cluster status %s", response.toJSON("prettyPrint")); + return response; + } + + /** + * This method is not supported anymore. + */ + @Deprecated + public synchronized OServerAdmin copyDatabase(final String databaseName, final String iDatabaseUserName, + final String iDatabaseUserPassword, final String iRemoteName, final String iRemoteEngine) throws IOException { + + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + + try { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_COPY, session); + network.writeString(databaseName); + network.writeString(iDatabaseUserName); + network.writeString(iDatabaseUserPassword); + network.writeString(iRemoteName); + network.writeString(iRemoteEngine); + } finally { + storage.endRequest(network); + } + + storage.getResponse(network, session); + + OLogManager.instance().debug(this, "Database '%s' has been copied to the server '%s'", databaseName, iRemoteName); + return null; + } + }, "Cannot copy the database: " + databaseName); + + return this; + } + + public synchronized Map getGlobalConfigurations() throws IOException { + return networkAdminOperation(new OStorageRemoteOperation>() { + @Override + public Map execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + final Map config = new HashMap(); + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_CONFIG_LIST, session); + storage.endRequest(network); + + try { + storage.beginResponse(network, session); + final int num = network.readShort(); + for (int i = 0; i < num; ++i) + config.put(network.readString(), network.readString()); + } finally { + storage.endResponse(network); + } + + return config; + } + }, "Cannot retrieve the configuration list"); + + } + + public synchronized String getGlobalConfiguration(final OGlobalConfiguration config) throws IOException { + return networkAdminOperation(new OStorageRemoteOperation() { + @Override + public String execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_CONFIG_GET, session); + network.writeString(config.getKey()); + storage.endRequest(network); + + try { + storage.beginResponse(network, session); + return network.readString(); + } finally { + storage.endResponse(network); + } + } + }, "Cannot retrieve the configuration value: " + config.getKey()); + } + + public synchronized OServerAdmin setGlobalConfiguration(final OGlobalConfiguration config, final Object iValue) + throws IOException { + + networkAdminOperation(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + storage.beginRequest(network, OChannelBinaryProtocol.REQUEST_CONFIG_SET, session); + network.writeString(config.getKey()); + network.writeString(iValue != null ? iValue.toString() : ""); + storage.endRequest(network); + storage.getResponse(network, session); + + return null; + } + }, "Cannot set the configuration value: " + config.getKey()); + return this; + } + + /** + * Close the connection if open. + */ + public synchronized void close() { + storage.close(); + } + + public synchronized void close(boolean iForce) { + storage.close(iForce, false); + } + + public synchronized String getURL() { + return storage != null ? storage.getURL() : null; + } + + public boolean isConnected() { + return storage != null && !storage.isClosed(); + } + + protected ODocument sendRequest(final byte iRequest, final ODocument iPayLoad, final String iActivity) { + // Using here networkOperation because the original retry logic was lik networkOperation + return storage.networkOperation(new OStorageRemoteOperation() { + @Override + public ODocument execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + storage.beginRequest(network, iRequest, session); + network.writeBytes(iPayLoad.toStream()); + } finally { + storage.endRequest(network); + } + + try { + storage.beginResponse(network, session); + return new ODocument(network.readBytes()); + } finally { + storage.endResponse(network); + } + } + }, "Error on executing '" + iActivity + "'"); + } + + private boolean handleDBFreeze() { + boolean retry; + OLogManager.instance().warn(this, + "DB is frozen will wait for " + OGlobalConfiguration.CLIENT_DB_RELEASE_WAIT_TIMEOUT.getValue() + " ms. and then retry."); + retry = true; + try { + Thread.sleep(OGlobalConfiguration.CLIENT_DB_RELEASE_WAIT_TIMEOUT.getValueAsInteger()); + } catch (InterruptedException ie) { + retry = false; + + Thread.currentThread().interrupt(); + } + return retry; + } + + protected T networkAdminOperation(final OStorageRemoteOperation operation, final String errorMessage) { + + OChannelBinaryAsynchClient network = null; + try { + //TODO:replace this api with one that get connection for only the specified url. + String serverUrl = storage.getNextAvailableServerURL(false, session); + do { + try { + network = storage.getNetwork(serverUrl); + } catch (OException e) { + serverUrl = storage.useNewServerURL(serverUrl); + if (serverUrl == null) + throw e; + } + } while (network == null); + + + T res = operation.execute(network, storage.getCurrentSession()); + storage.connectionManager.release(network); + return res; + } catch (Exception e) { + if(network != null) + storage.connectionManager.release(network); + storage.close(true, false); + throw OException.wrapException(new OStorageException(errorMessage), e); + } + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemote.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemote.java new file mode 100755 index 00000000000..89a53228e9e --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemote.java @@ -0,0 +1,2520 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.concur.OOfflineNodeException; +import com.orientechnologies.common.concur.lock.OInterruptedException; +import com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.OConstants; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.command.OCommandRequestAsynch; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategy; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTxInternal; +import com.orientechnologies.orient.core.db.record.OCurrentStorageComponentsFactory; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeCollectionManager; +import com.orientechnologies.orient.core.exception.*; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.security.OTokenException; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.OCredentialInterceptor; +import com.orientechnologies.orient.core.security.OSecurityManager; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerAnyStreamable; +import com.orientechnologies.orient.core.sql.query.OBasicResultSet; +import com.orientechnologies.orient.core.sql.query.OLiveQuery; +import com.orientechnologies.orient.core.sql.query.OLiveResultListener; +import com.orientechnologies.orient.core.storage.*; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext; +import com.orientechnologies.orient.core.tx.OTransaction; +import com.orientechnologies.orient.core.tx.OTransactionAbstract; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.ODistributedRedirectException; +import com.orientechnologies.orient.enterprise.channel.binary.OTokenSecurityException; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This object is bound to each remote ODatabase instances. + */ +public class OStorageRemote extends OStorageAbstract implements OStorageProxy { + public static final String PARAM_CONNECTION_STRATEGY = "connectionStrategy"; + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 2424; + private static final int DEFAULT_SSL_PORT = 2434; + private static final String ADDRESS_SEPARATOR = ";"; + public static final String DRIVER_NAME = "OrientDB Java"; + private static final String LOCAL_IP = "127.0.0.1"; + private static final String LOCALHOST = "localhost"; + private static AtomicInteger sessionSerialId = new AtomicInteger(-1); + + public enum CONNECTION_STRATEGY { + STICKY, ROUND_ROBIN_CONNECT, ROUND_ROBIN_REQUEST + } + + private CONNECTION_STRATEGY connectionStrategy = CONNECTION_STRATEGY.STICKY; + + private final OSBTreeCollectionManagerRemote sbTreeCollectionManager = new OSBTreeCollectionManagerRemote(this); + protected final List serverURLs = new ArrayList(); + protected final Map clusterMap = new ConcurrentHashMap(); + private final ExecutorService asynchExecutor; + private final ODocument clusterConfiguration = new ODocument(); + private final String clientId; + private final AtomicInteger users = new AtomicInteger(0); + private OContextConfiguration clientConfiguration; + private int connectionRetry; + private int connectionRetryDelay; + private OCluster[] clusters = OCommonConst.EMPTY_CLUSTER_ARRAY; + private int defaultClusterId; + private OStorageRemoteAsynchEventListener asynchEventListener; + private Map connectionOptions; + private String recordFormat; + protected ORemoteConnectionManager connectionManager; + private final Set sessions = Collections + .newSetFromMap(new ConcurrentHashMap()); + + public OStorageRemote(final String iClientId, final String iURL, final String iMode) throws IOException { + this(iClientId, iURL, iMode, null, true); + } + + public OStorageRemote(final String iClientId, final String iURL, final String iMode, final STATUS status, + final boolean managePushMessages) throws IOException { + super(iURL, iURL, iMode, 0); // NO TIMEOUT @SINCE 1.5 + if (status != null) + this.status = status; + + clientId = iClientId; + configuration = null; + + clientConfiguration = new OContextConfiguration(); + connectionRetry = clientConfiguration.getValueAsInteger(OGlobalConfiguration.NETWORK_SOCKET_RETRY); + connectionRetryDelay = clientConfiguration.getValueAsInteger(OGlobalConfiguration.NETWORK_SOCKET_RETRY_DELAY); + if (managePushMessages) + asynchEventListener = new OStorageRemoteAsynchEventListener(this); + parseServerURLs(); + + asynchExecutor = Executors.newSingleThreadScheduledExecutor(); + + OEngineRemote engine = (OEngineRemote) Orient.instance().getRunningEngine(OEngineRemote.NAME); + connectionManager = engine.getConnectionManager(); + } + + public T asyncNetworkOperation(final OStorageRemoteOperationWrite write, final OStorageRemoteOperationRead read, int mode, + final ORecordId recordId, final ORecordCallback callback, final String errorMessage) { + final int pMode; + if (mode == 1 && callback == null) + // ASYNCHRONOUS MODE NO ANSWER + pMode = 2; + else + pMode = mode; + return baseNetworkOperation(new OStorageRemoteOperation() { + @Override + public T execute(final OChannelBinaryAsynchClient network, final OStorageRemoteSession session) throws IOException { + // Send The request + write.execute(network, session, pMode); + final T res; + if (pMode == 0) { + // SYNC + res = read.execute(network, session); + connectionManager.release(network); + } else if (pMode == 1) { + // ASYNC + res = null; + OStorageRemote.this.asynchExecutor.submit(new Runnable() { + @Override + public void run() { + try { + T inRes = read.execute(network, session); + callback.call(recordId, inRes); + connectionManager.release(network); + } catch (Throwable e) { + connectionManager.remove(network); + OLogManager.instance().error(this, "Exception on async query", e); + } + } + }); + } else { + // NO RESPONSE + connectionManager.release(network); + res = null; + } + return res; + } + }, errorMessage, connectionRetry); + } + + public T networkOperationRetry(final OStorageRemoteOperation operation, final String errorMessage, int retry) { + return baseNetworkOperation(new OStorageRemoteOperation() { + @Override + public T execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + final T res = operation.execute(network, session); + connectionManager.release(network); + return res; + } + }, errorMessage, retry); + } + + public T networkOperation(final OStorageRemoteOperation operation, final String errorMessage) { + return networkOperationRetry(operation, errorMessage, connectionRetry); + } + + public T baseNetworkOperation(final OStorageRemoteOperation operation, final String errorMessage, int retry) { + OStorageRemoteSession session = getCurrentSession(); + if (session.commandExecuting) + throw new ODatabaseException( + "Cannot execute the request because an asynchronous operation is in progress. Please use a different connection"); + + String serverUrl = null; + do { + OChannelBinaryAsynchClient network = null; + + if (serverUrl == null) + serverUrl = getNextAvailableServerURL(false, session); + + do { + try { + network = getNetwork(serverUrl); + } catch (OException e) { + serverUrl = useNewServerURL(serverUrl); + if (serverUrl == null) + throw e; + } + } while (network == null); + + try { + // In case i do not have a token or i'm switching between server i've to execute a open operation. + OStorageRemoteNodeSession nodeSession = session.getServerSession(network.getServerURL()); + if (nodeSession == null || !nodeSession.isValid()) { + openRemoteDatabase(network); + if (!network.tryLock()) { + connectionManager.release(network); + continue; + } + } + + return operation.execute(network, session); + } catch (ODistributedRedirectException e) { + connectionManager.release(network); + OLogManager.instance() + .debug(this, "Redirecting the request from server '%s' to the server '%s' because %s", e.getFromServer(), e.toString(), + e.getMessage()); + + // RECONNECT TO THE SERVER SUGGESTED IN THE EXCEPTION + serverUrl = e.getToServerAddress(); + + } catch (OModificationOperationProhibitedException mope) { + connectionManager.release(network); + handleDBFreeze(); + serverUrl = null; + } catch (OTokenException e) { + connectionManager.release(network); + session.removeServerSession(network.getServerURL()); + if (--retry <= 0) + throw OException.wrapException(new OStorageException(errorMessage), e); + serverUrl = null; + } catch (OTokenSecurityException e) { + connectionManager.release(network); + session.removeServerSession(network.getServerURL()); + if (--retry <= 0) + throw OException.wrapException(new OStorageException(errorMessage), e); + serverUrl = null; + } catch (OOfflineNodeException e) { + connectionManager.release(network); + // Remove the current url because the node is offline + synchronized (serverURLs) { + serverURLs.remove(serverUrl); + } + for (OStorageRemoteSession activeSession : sessions) { + // Not thread Safe ... + activeSession.removeServerSession(serverUrl); + } + serverUrl = null; + + } catch (IOException e) { + connectionManager.release(network); + retry = handleIOException(retry, network, e); + serverUrl = null; + } catch (OIOException e) { + connectionManager.release(network); + retry = handleIOException(retry, network, e); + serverUrl = null; + } catch (OException e) { + connectionManager.release(network); + throw e; + } catch (Exception e) { + connectionManager.release(network); + throw OException.wrapException(new OStorageException(errorMessage), e); + } + } while (true); + + } + + private int handleIOException(int retry, final OChannelBinaryAsynchClient network, final Exception e) { + OLogManager.instance() + .info(this, "Caught Network I/O errors on %s, trying an automatic reconnection... (error: %s)", network.getServerURL(), + e.getMessage()); + OLogManager.instance().debug(this, "I/O error stack: ", e); + connectionManager.remove(network); + if (--retry <= 0) + throw OException.wrapException(new OIOException(e.getMessage()), e); + else { + try { + Thread.sleep(connectionRetryDelay); + } catch (InterruptedException e1) { + throw OException.wrapException(new OInterruptedException(e1.getMessage()), e); + } + } + return retry; + } + + @Override + public boolean isAssigningClusterIds() { + return false; + } + + public int getSessionId() { + OStorageRemoteSession session = getCurrentSession(); + return session != null ? session.getSessionId() : -1; + } + + public String getServerURL() { + OStorageRemoteSession session = getCurrentSession(); + return session != null ? session.getServerUrl() : null; + } + + public void open(final String iUserName, final String iUserPassword, final Map iOptions) { + + stateLock.acquireWriteLock(); + addUser(); + try { + OStorageRemoteSession session = getCurrentSession(); + if (status == STATUS.CLOSED || !iUserName.equals(session.connectionUserName) || !iUserPassword + .equals(session.connectionUserPassword) || session.sessions.isEmpty()) { + + OCredentialInterceptor ci = OSecurityManager.instance().newCredentialInterceptor(); + + if (ci != null) { + ci.intercept(getURL(), iUserName, iUserPassword); + session.connectionUserName = ci.getUsername(); + session.connectionUserPassword = ci.getPassword(); + } else // Do Nothing + { + session.connectionUserName = iUserName; + session.connectionUserPassword = iUserPassword; + } + + parseOptions(iOptions); + + openRemoteDatabase(); + + final OStorageConfiguration storageConfiguration = new OStorageRemoteConfiguration(this, recordFormat); + storageConfiguration.load(iOptions); + + configuration = storageConfiguration; + + componentsFactory = new OCurrentStorageComponentsFactory(configuration); + + } else { + reopenRemoteDatabase(); + } + } catch (Exception e) { + removeUser(); + if (e instanceof RuntimeException) + // PASS THROUGH + throw (RuntimeException) e; + else + throw OException.wrapException(new OStorageException("Cannot open the remote storage: " + name), e); + + } finally { + stateLock.releaseWriteLock(); + } + } + + private void parseOptions(final Map iOptions) { + if (iOptions == null || iOptions.size() == 0) + return; + + final Object connType = iOptions.get(PARAM_CONNECTION_STRATEGY.toLowerCase(Locale.ENGLISH)); + if (connType != null) + connectionStrategy = CONNECTION_STRATEGY.valueOf(connType.toString().toUpperCase(Locale.ENGLISH)); + + // CREATE A COPY TO AVOID POST OPEN MANIPULATION BY USER + connectionOptions = new HashMap(iOptions); + } + + @Override + public OSBTreeCollectionManager getSBtreeCollectionManager() { + return sbTreeCollectionManager; + } + + public void reload() { + networkOperation(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + stateLock.acquireWriteLock(); + try { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_RELOAD, session); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + readDatabaseInformation(network); + } finally { + endResponse(network); + } + configuration.load(new HashMap()); + return null; + } finally { + stateLock.releaseWriteLock(); + } + } + }, "Error on reloading database information"); + } + + public void create(final Map iOptions) { + throw new UnsupportedOperationException( + "Cannot create a database in a remote server. Please use the console or the OServerAdmin class."); + } + + public boolean exists() { + throw new UnsupportedOperationException( + "Cannot check the existence of a database in a remote server. Please use the console or the OServerAdmin class."); + } + + public void close(final boolean iForce, boolean onDelete) { + if (status == STATUS.CLOSED) + return; + + stateLock.acquireWriteLock(); + try { + if (status == STATUS.CLOSED) + return; + + final OStorageRemoteSession session = getCurrentSession(); + if (session != null) { + final Collection nodes = session.getAllServerSessions(); + if (!nodes.isEmpty()) { + for (OStorageRemoteNodeSession nodeSession : nodes) { + OChannelBinaryAsynchClient network = null; + try { + network = getNetwork(nodeSession.getServerURL()); + network.beginRequest(OChannelBinaryProtocol.REQUEST_DB_CLOSE, session); + endRequest(network); + connectionManager.release(network); + } catch (OIOException ex) { + // IGNORING IF THE SERVER IS DOWN OR NOT REACHABLE THE SESSION IS AUTOMATICALLY CLOSED. + OLogManager.instance().debug(this, "Impossible to comunicate to the server for close: %s", ex); + connectionManager.remove(network); + } catch (IOException ex) { + // IGNORING IF THE SERVER IS DOWN OR NOT REACHABLE THE SESSION IS AUTOMATICALLY CLOSED. + OLogManager.instance().debug(this, "Impossible to comunicate to the server for close: %s", ex); + connectionManager.remove(network); + } + } + session.close(); + sessions.remove(session); + if (!checkForClose(iForce)) + return; + } else { + if (!iForce) + return; + } + } + + status = STATUS.CLOSING; + // CLOSE ALL THE CONNECTIONS + for (String url : serverURLs) { + connectionManager.closePool(url); + } + sbTreeCollectionManager.close(); + + super.close(iForce, onDelete); + status = STATUS.CLOSED; + + Orient.instance().unregisterStorage(this); + } finally { + stateLock.releaseWriteLock(); + } + } + + private boolean checkForClose(final boolean force) { + if (status == STATUS.CLOSED) + return false; + + if (status == STATUS.CLOSED) + return false; + + final int remainingUsers = getUsers() > 0 ? removeUser() : 0; + + return force || remainingUsers == 0; + } + + @Override + public int getUsers() { + return users.get(); + } + + @Override + public int addUser() { + return users.incrementAndGet(); + } + + @Override + public int removeUser() { + if (users.get() < 1) + throw new IllegalStateException("Cannot remove user of the remote storage '" + toString() + "' because no user is using it"); + + return users.decrementAndGet(); + } + + public void delete() { + throw new UnsupportedOperationException( + "Cannot delete a database in a remote server. Please use the console or the OServerAdmin class."); + } + + public Set getClusterNames() { + stateLock.acquireReadLock(); + try { + + return new HashSet(clusterMap.keySet()); + + } finally { + stateLock.releaseReadLock(); + } + } + + public OStorageOperationResult createRecord(final ORecordId iRid, final byte[] iContent, + final int iRecordVersion, final byte iRecordType, final int iMode, final ORecordCallback iCallback) { + + final OSBTreeCollectionManager collectionManager = ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager(); + ORecordCallback realCallback = null; + if (iCallback != null) { + realCallback = new ORecordCallback() { + @Override + public void call(ORecordId iRID, OPhysicalPosition iParameter) { + iCallback.call(iRID, iParameter.clusterPosition); + } + }; + } + final ORecordId idCopy = iRid.copy(); + // The Upper layer require to return this also if it not really receive response from the network + final OPhysicalPosition ppos = new OPhysicalPosition(iRecordType); + asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(final OChannelBinaryAsynchClient network, final OStorageRemoteSession session, int mode) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_CREATE, session); + network.writeShort((short) iRid.getClusterId()); + network.writeBytes(iContent); + network.writeByte(iRecordType); + network.writeByte((byte) mode); + } finally { + endRequest(network); + } + } + }, new OStorageRemoteOperationRead() { + @Override + public OPhysicalPosition execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + + // SYNCHRONOUS + try { + beginResponse(network, session); + + // FIRST READ THE ENTIRE RESPONSE + short clusterId = network.readShort(); + final long clPos = network.readLong(); + final int recVer = network.readVersion(); + final Map> collectionChanges = readCollectionChanges(network); + + // APPLY CHANGES + ppos.clusterPosition = clPos; + ppos.recordVersion = recVer; + + // THIS IS A COMPATIBILITY FIX TO AVOID TO FILL THE CLUSTER ID IN CASE OF ASYNC + if (iMode == 0) { + iRid.setClusterId(clusterId); + iRid.setClusterPosition(ppos.clusterPosition); + } + idCopy.setClusterId(clusterId); + idCopy.setClusterPosition(ppos.clusterPosition); + + updateCollection(collectionChanges, collectionManager); + return ppos; + + } finally { + endResponse(network); + } + } + }, iMode, idCopy, realCallback, "Error on create record in cluster " + iRid.getClusterId()); + + return new OStorageOperationResult(ppos); + } + + @Override + public ORecordMetadata getRecordMetadata(final ORID rid) { + + return networkOperation(new OStorageRemoteOperation() { + @Override + public ORecordMetadata execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_METADATA, session); + network.writeRID(rid); + } finally { + endRequest(network); + } + try { + beginResponse(network, session); + final ORID responseRid = network.readRID(); + final int responseVersion = network.readVersion(); + + return new ORecordMetadata(responseRid, responseVersion); + } finally { + endResponse(network); + } + } + }, "Error on record metadata read " + rid); + } + + @Override + public OStorageOperationResult readRecordIfVersionIsNotLatest(final ORecordId rid, final String fetchPlan, + final boolean ignoreCache, final int recordVersion) throws ORecordNotFoundException { + if (getCurrentSession().commandExecuting) + // PENDING NETWORK OPERATION, CAN'T EXECUTE IT NOW + return new OStorageOperationResult(null); + + return networkOperation(new OStorageRemoteOperation>() { + @Override + public OStorageOperationResult execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_LOAD_IF_VERSION_NOT_LATEST, session); + network.writeRID(rid); + network.writeVersion(recordVersion); + network.writeString(fetchPlan != null ? fetchPlan : ""); + network.writeByte((byte) (ignoreCache ? 1 : 0)); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + + if (network.readByte() == 0) + return new OStorageOperationResult(null); + + byte type = network.readByte(); + int recVersion = network.readVersion(); + byte[] bytes = network.readBytes(); + ORawBuffer buffer = new ORawBuffer(bytes, recVersion, type); + + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + ORecord record; + + while (network.readByte() == 2) { + record = (ORecord) OChannelBinaryProtocol.readIdentifiable(network); + + if (database != null) + // PUT IN THE CLIENT LOCAL CACHE + database.getLocalCache().updateRecord(record); + } + return new OStorageOperationResult(buffer); + + } finally { + endResponse(network); + } + } + }, "Error on read record " + rid); + } + + public OStorageOperationResult readRecord(final ORecordId iRid, final String iFetchPlan, final boolean iIgnoreCache, + boolean prefetchRecords, final ORecordCallback iCallback) { + + if (getCurrentSession().commandExecuting) + // PENDING NETWORK OPERATION, CAN'T EXECUTE IT NOW + return new OStorageOperationResult(null); + + return networkOperation(new OStorageRemoteOperation>() { + @Override + public OStorageOperationResult execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_LOAD, session); + network.writeRID(iRid); + network.writeString(iFetchPlan != null ? iFetchPlan : ""); + if (network.getSrvProtocolVersion() >= 9) + network.writeByte((byte) (iIgnoreCache ? 1 : 0)); + + if (network.getSrvProtocolVersion() >= 13) + network.writeByte((byte) 0); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + + if (network.readByte() == 0) + return new OStorageOperationResult(null); + + final ORawBuffer buffer; + if (network.getSrvProtocolVersion() <= 27) + buffer = new ORawBuffer(network.readBytes(), network.readVersion(), network.readByte()); + else { + final byte type = network.readByte(); + final int recVersion = network.readVersion(); + final byte[] bytes = network.readBytes(); + buffer = new ORawBuffer(bytes, recVersion, type); + } + + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + ORecord record; + while (network.readByte() == 2) { + record = (ORecord) OChannelBinaryProtocol.readIdentifiable(network); + + if (database != null) + // PUT IN THE CLIENT LOCAL CACHE + database.getLocalCache().updateRecord(record); + } + return new OStorageOperationResult(buffer); + + } finally { + endResponse(network); + } + + } + }, "Error on read record " + iRid); + } + + @Override + public String incrementalBackup(final String backupDirectory) { + return networkOperation(new OStorageRemoteOperation() { + @Override + public String execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + network = beginRequest(network, OChannelBinaryProtocol.REQUEST_INCREMENTAL_BACKUP, session); + network.writeString(backupDirectory); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + String fileName = network.readString(); + return fileName; + } finally { + endResponse(network); + } + + } + }, "Error on incremental backup"); + } + + @Override + public void restoreFromIncrementalBackup(final String filePath) { + throw new UnsupportedOperationException("This operations is part of internal API and is not supported in remote storage"); + } + + public OStorageOperationResult updateRecord(final ORecordId iRid, final boolean updateContent, final byte[] iContent, + final int iVersion, final byte iRecordType, final int iMode, final ORecordCallback iCallback) { + final OSBTreeCollectionManager collectionManager = ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager(); + + Integer resVersion = asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(final OChannelBinaryAsynchClient network, final OStorageRemoteSession session, int mode) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_UPDATE, session); + network.writeRID(iRid); + network.writeBoolean(updateContent); + network.writeBytes(iContent); + network.writeVersion(iVersion); + network.writeByte(iRecordType); + network.writeByte((byte) mode); + } finally { + endRequest(network); + } + } + }, new OStorageRemoteOperationRead() { + @Override + public Integer execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginResponse(network, session); + final Integer r = network.readVersion(); + final Map> collectionChanges = readCollectionChanges(network); + + updateCollection(collectionChanges, collectionManager); + + return r; + + } finally { + endResponse(network); + } + } + }, iMode, iRid, iCallback, "Error on update record " + iRid); + + if (resVersion == null) + // Returning given version in case of no answer from server + resVersion = iVersion; + return new OStorageOperationResult(resVersion); + } + + @Override + public void recyclePosition(final ORecordId iRecordId, final byte[] content, final int recordVersion, final byte recordType) { + throw new UnsupportedOperationException("recyclePosition"); + } + + public OStorageOperationResult deleteRecord(final ORecordId iRid, final int iVersion, final int iMode, + final ORecordCallback iCallback) { + Boolean resDelete = asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_DELETE, session); + network.writeRID(iRid); + network.writeVersion(iVersion); + network.writeByte((byte) mode); + } finally { + endRequest(network); + } + } + }, new OStorageRemoteOperationRead() { + @Override + public Boolean execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginResponse(network, session); + return network.readByte() == 1; + } finally { + endResponse(network); + } + } + }, iMode, iRid, iCallback, "Error on delete record " + iRid); + return new OStorageOperationResult(resDelete); + } + + @Override + public OStorageOperationResult hideRecord(final ORecordId recordId, final int mode, + final ORecordCallback callback) { + Boolean resHide = asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(final OChannelBinaryAsynchClient network, final OStorageRemoteSession session, int mode) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_HIDE, session); + network.writeRID(recordId); + network.writeByte((byte) mode); + } finally { + endRequest(network); + } + } + }, new OStorageRemoteOperationRead() { + @Override + public Boolean execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginResponse(network, session); + return network.readByte() == 1; + } finally { + endResponse(network); + } + } + }, mode, recordId, callback, "Error on hide record " + recordId); + return new OStorageOperationResult(resHide); + } + + @Override + public boolean cleanOutRecord(final ORecordId recordId, final int recordVersion, final int iMode, + final ORecordCallback callback) { + + return asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_RECORD_CLEAN_OUT, session); + network.writeRID(recordId); + network.writeVersion(recordVersion); + network.writeByte((byte) mode); + } finally { + endRequest(network); + } + } + }, new OStorageRemoteOperationRead() { + @Override + public Boolean execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginResponse(network, session); + return network.readByte() == 1; + } finally { + endResponse(network); + } + } + }, iMode, recordId, callback, "Error on delete record " + recordId); + } + + @Override + public List backup(OutputStream out, Map options, Callable callable, + final OCommandOutputListener iListener, int compressionLevel, int bufferSize) throws IOException { + throw new UnsupportedOperationException( + "backup is not supported against remote storage. Open the database with plocal or use the incremental backup in the Enterprise Edition"); + } + + @Override + public void restore(InputStream in, Map options, Callable callable, + final OCommandOutputListener iListener) throws IOException { + throw new UnsupportedOperationException( + "restore is not supported against remote storage. Open the database with plocal or use Enterprise Edition"); + } + + public OContextConfiguration getClientConfiguration() { + return clientConfiguration; + } + + public long count(final int iClusterId) { + return count(new int[] { iClusterId }); + } + + @Override + public long count(int iClusterId, boolean countTombstones) { + return count(new int[] { iClusterId }, countTombstones); + } + + public long[] getClusterDataRange(final int iClusterId) { + + return networkOperation(new OStorageRemoteOperation() { + @Override + public long[] execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_DATARANGE, session); + + network.writeShort((short) iClusterId); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + return new long[] { network.readLong(), network.readLong() }; + } finally { + endResponse(network); + } + + } + }, "Error on getting last entry position count in cluster: " + iClusterId); + } + + @Override + public OPhysicalPosition[] higherPhysicalPositions(final int iClusterId, final OPhysicalPosition iClusterPosition) { + + return networkOperation(new OStorageRemoteOperation() { + @Override + public OPhysicalPosition[] execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_POSITIONS_HIGHER, session); + network.writeInt(iClusterId); + network.writeLong(iClusterPosition.clusterPosition); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + final int positionsCount = network.readInt(); + + if (positionsCount == 0) { + return OCommonConst.EMPTY_PHYSICAL_POSITIONS_ARRAY; + } else { + return readPhysicalPositions(network, positionsCount); + } + + } finally { + endResponse(network); + } + + } + }, "Error on retrieving higher positions after " + iClusterPosition.clusterPosition); + } + + @Override + public OPhysicalPosition[] ceilingPhysicalPositions(final int clusterId, final OPhysicalPosition physicalPosition) { + + return networkOperation(new OStorageRemoteOperation() { + @Override + public OPhysicalPosition[] execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_POSITIONS_CEILING, session); + network.writeInt(clusterId); + network.writeLong(physicalPosition.clusterPosition); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + final int positionsCount = network.readInt(); + + if (positionsCount == 0) { + return OCommonConst.EMPTY_PHYSICAL_POSITIONS_ARRAY; + } else { + return readPhysicalPositions(network, positionsCount); + } + + } finally { + endResponse(network); + } + + } + }, "Error on retrieving ceiling positions after " + physicalPosition.clusterPosition); + } + + @Override + public OPhysicalPosition[] lowerPhysicalPositions(final int iClusterId, final OPhysicalPosition physicalPosition) { + return networkOperation(new OStorageRemoteOperation() { + @Override + public OPhysicalPosition[] execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_POSITIONS_LOWER, session); + network.writeInt(iClusterId); + network.writeLong(physicalPosition.clusterPosition); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + + final int positionsCount = network.readInt(); + + if (positionsCount == 0) { + return OCommonConst.EMPTY_PHYSICAL_POSITIONS_ARRAY; + } else { + return readPhysicalPositions(network, positionsCount); + } + + } finally { + endResponse(network); + } + } + }, "Error on retrieving lower positions after " + physicalPosition.clusterPosition); + } + + @Override + public OPhysicalPosition[] floorPhysicalPositions(final int clusterId, final OPhysicalPosition physicalPosition) { + return networkOperation(new OStorageRemoteOperation() { + @Override + public OPhysicalPosition[] execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) + throws IOException { + + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_POSITIONS_FLOOR, session); + network.writeInt(clusterId); + network.writeLong(physicalPosition.clusterPosition); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + + final int positionsCount = network.readInt(); + + if (positionsCount == 0) { + return OCommonConst.EMPTY_PHYSICAL_POSITIONS_ARRAY; + } else { + return readPhysicalPositions(network, positionsCount); + } + + } finally { + endResponse(network); + } + } + }, "Error on retrieving floor positions after " + physicalPosition.clusterPosition); + } + + public long getSize() { + return networkOperation(new OStorageRemoteOperation() { + @Override + public Long execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_SIZE, session); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + return network.readLong(); + } finally { + endResponse(network); + } + } + }, "Error on read database size"); + } + + @Override + public long countRecords() { + return networkOperation(new OStorageRemoteOperation() { + @Override + public Long execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_COUNTRECORDS, session); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + return network.readLong(); + } finally { + endResponse(network); + } + } + }, "Error on read database record count"); + } + + public long count(final int[] iClusterIds) { + return count(iClusterIds, false); + } + + public long count(final int[] iClusterIds, final boolean countTombstones) { + + return networkOperation(new OStorageRemoteOperation() { + @Override + public Long execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_COUNT, session); + + network.writeShort((short) iClusterIds.length); + for (int iClusterId : iClusterIds) + network.writeShort((short) iClusterId); + + if (network.getSrvProtocolVersion() >= 13) + network.writeByte(countTombstones ? (byte) 1 : (byte) 0); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + return network.readLong(); + } finally { + endResponse(network); + } + } + }, "Error on read record count in clusters: " + Arrays.toString(iClusterIds)); + } + + /** + * Execute the command remotely and get the results back. + */ + public Object command(final OCommandRequestText iCommand) { + + if (!(iCommand instanceof OSerializableStream)) + throw new OCommandExecutionException("Cannot serialize the command to be executed to the server side."); + final boolean live = iCommand instanceof OLiveQuery; + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.get(); + + return networkOperation(new OStorageRemoteOperation() { + @Override + public Object execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + Object result = null; + session.commandExecuting = true; + try { + + final boolean asynch = iCommand instanceof OCommandRequestAsynch && ((OCommandRequestAsynch) iCommand).isAsynchronous(); + + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_COMMAND, session); + + if (live) { + network.writeByte((byte) 'l'); + } else { + network.writeByte((byte) (asynch ? 'a' : 's')); // ASYNC / SYNC + } + network.writeBytes(OStreamSerializerAnyStreamable.INSTANCE.toStream(iCommand)); + + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + + // Collection of prefetched temporary record (nested projection record), to refer for avoid garbage collection. + List temporaryResults = new ArrayList(); + + boolean addNextRecord = true; + + if (asynch) { + byte status; + + // ASYNCH: READ ONE RECORD AT TIME + while ((status = network.readByte()) > 0) { + final ORecord record = (ORecord) OChannelBinaryProtocol.readIdentifiable(network); + if (record == null) + continue; + + switch (status) { + case 1: + // PUT AS PART OF THE RESULT SET. INVOKE THE LISTENER + if (addNextRecord) { + addNextRecord = iCommand.getResultListener().result(record); + database.getLocalCache().updateRecord(record); + } + break; + + case 2: + if (record.getIdentity().getClusterId() == -2) + temporaryResults.add(record); + // PUT IN THE CLIENT LOCAL CACHE + database.getLocalCache().updateRecord(record); + } + } + } else { + result = readSynchResult(network, database, temporaryResults); + if (live) { + final ODocument doc = ((List) result).get(0); + final Integer token = doc.field("token"); + final Boolean unsubscribe = doc.field("unsubscribe"); + if (token != null) { + if (Boolean.TRUE.equals(unsubscribe)) { + if (OStorageRemote.this.asynchEventListener != null) + OStorageRemote.this.asynchEventListener.unregisterLiveListener(token); + } else { + final OLiveResultListener listener = (OLiveResultListener) iCommand.getResultListener(); + ODatabaseDocumentInternal current = ODatabaseRecordThreadLocal.INSTANCE.get(); + final ODatabaseDocument dbCopy = current.copy(); + ORemoteConnectionPool pool = OStorageRemote.this.connectionManager.getPool(network.getServerURL()); + OStorageRemote.this.asynchEventListener.registerLiveListener(pool, token, new OLiveResultListener() { + + @Override + public void onUnsubscribe(int iLiveToken) { + listener.onUnsubscribe(iLiveToken); + dbCopy.close(); + } + + @Override + public void onLiveResult(int iLiveToken, ORecordOperation iOp) throws OException { + dbCopy.activateOnCurrentThread(); + listener.onLiveResult(iLiveToken, iOp); + } + + @Override + public void onError(int iLiveToken) { + listener.onError(iLiveToken); + dbCopy.close(); + } + }); + } + } else { + throw new OStorageException("Cannot execute live query, returned null token"); + } + } + } + if (!temporaryResults.isEmpty()) { + if (result instanceof OBasicResultSet) { + ((OBasicResultSet) result).setTemporaryRecordCache(temporaryResults); + } + } + return result; + } finally { + endResponse(network); + } + } finally { + session.commandExecuting = false; + if (iCommand.getResultListener() != null && !live) + iCommand.getResultListener().end(); + } + + } + }, "Error on executing command: " + iCommand); + } + + protected Object readSynchResult(final OChannelBinaryAsynchClient network, final ODatabaseDocument database, + List temporaryResults) throws IOException { + + final Object result; + + final byte type = network.readByte(); + switch (type) { + case 'n': + result = null; + break; + + case 'r': + result = OChannelBinaryProtocol.readIdentifiable(network); + if (result instanceof ORecord) + database.getLocalCache().updateRecord((ORecord) result); + break; + + case 'l': + case 's': + final int tot = network.readInt(); + final Collection coll; + + coll = type == 's' ? new HashSet(tot) : new OBasicResultSet(tot); + for (int i = 0; i < tot; ++i) { + final OIdentifiable resultItem = OChannelBinaryProtocol.readIdentifiable(network); + if (resultItem instanceof ORecord) + database.getLocalCache().updateRecord((ORecord) resultItem); + coll.add(resultItem); + } + + result = coll; + break; + case 'i': + coll = new OBasicResultSet(); + byte status; + while ((status = network.readByte()) > 0) { + final OIdentifiable record = OChannelBinaryProtocol.readIdentifiable(network); + if (record == null) + continue; + if (status == 1) { + if (record instanceof ORecord) + database.getLocalCache().updateRecord((ORecord) record); + coll.add(record); + } + } + result = coll; + break; + case 'w': + final OIdentifiable record = OChannelBinaryProtocol.readIdentifiable(network); + // ((ODocument) record).setLazyLoad(false); + result = ((ODocument) record).field("result"); + break; + + default: + OLogManager.instance().warn(this, "Received unexpected result from query: %d", type); + result = null; + } + + if (network.getSrvProtocolVersion() >= 17) { + // LOAD THE FETCHED RECORDS IN CACHE + byte status; + while ((status = network.readByte()) > 0) { + final ORecord record = (ORecord) OChannelBinaryProtocol.readIdentifiable(network); + if (record != null && status == 2) { + // PUT IN THE CLIENT LOCAL CACHE + database.getLocalCache().updateRecord(record); + if (record.getIdentity().getClusterId() == -2) + temporaryResults.add(record); + } + } + } + + return result; + } + + public List commit(final OTransaction iTx, final Runnable callback) { + networkOperation(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + session.commandExecuting = true; + + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_TX_COMMIT, session); + + network.writeInt(iTx.getId()); + network.writeByte((byte) (iTx.isUsingLog() ? 1 : 0)); + + for (ORecordOperation txEntry : iTx.getAllRecordEntries()) { + commitEntry(network, txEntry); + } + + // END OF RECORD ENTRIES + network.writeByte((byte) 0); + + // SEND EMPTY TX CHANGES, TRACKING MADE SERVER SIDE + network.writeBytes(iTx.getIndexChanges().toStream()); + } finally { + endRequest(network); + } + + try { + // READ THE ENTIRE RESPONSE FIRST + beginResponse(network, session); + + // NEW RECORDS + final int createdRecords = network.readInt(); + final Map createdRecordsMap = new HashMap(createdRecords); + for (int i = 0; i < createdRecords; i++) + createdRecordsMap.put(network.readRID(), network.readRID()); + + // UPDATED RECORDS + final int updatedRecords = network.readInt(); + final Map updatedRecordsMap = new HashMap(updatedRecords); + + for (int i = 0; i < updatedRecords; ++i) + updatedRecordsMap.put(network.readRID(), network.readVersion()); + + Map> collectionChanges = null; + if (network.getSrvProtocolVersion() >= 20) + collectionChanges = readCollectionChanges(network); + + // APPLY CHANGES + for (Map.Entry entry : createdRecordsMap.entrySet()) + iTx.updateIdentityAfterCommit(entry.getKey(), entry.getValue()); + createdRecordsMap.clear(); + + for (Map.Entry entry : updatedRecordsMap.entrySet()) { + final ORecordOperation rop = iTx.getRecordEntry(entry.getKey()); + if (rop != null) { + if (entry.getValue() > rop.getRecord().getVersion() + 1) + // IN CASE OF REMOTE CONFLICT STRATEGY FORCE UNLOAD DUE TO INVALID CONTENT + rop.getRecord().unload(); + ORecordInternal.setVersion(rop.getRecord(), entry.getValue()); + } + } + updatedRecordsMap.clear(); + + if (collectionChanges != null) + updateCollection(collectionChanges, ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager()); + + } finally { + endResponse(network); + } + + // SET ALL THE RECORDS AS UNDIRTY + for (ORecordOperation txEntry : iTx.getAllRecordEntries()) + ORecordInternal.unsetDirty(txEntry.getRecord()); + + // UPDATE THE CACHE ONLY IF THE ITERATOR ALLOWS IT. USE THE STRATEGY TO ALWAYS REMOVE ALL THE RECORDS SINCE THEY COULD BE + // CHANGED AS CONTENT IN CASE OF TREE AND GRAPH DUE TO CROSS REFERENCES + OTransactionAbstract.updateCacheFromEntries(iTx, iTx.getAllRecordEntries(), true); + + return null; + } finally + + { + session.commandExecuting = false; + } + } + }, "Error on commit"); + return null; + } + + public void rollback(OTransaction iTx) { + } + + public int getClusterIdByName(final String iClusterName) { + stateLock.acquireReadLock(); + try { + + if (iClusterName == null) + return -1; + + if (Character.isDigit(iClusterName.charAt(0))) + return Integer.parseInt(iClusterName); + + final OCluster cluster = clusterMap.get(iClusterName.toLowerCase(Locale.ENGLISH)); + if (cluster == null) + return -1; + + return cluster.getId(); + } finally { + stateLock.releaseReadLock(); + } + } + + public int getDefaultClusterId() { + return defaultClusterId; + } + + public void setDefaultClusterId(int defaultClusterId) { + this.defaultClusterId = defaultClusterId; + } + + public int addCluster(final String iClusterName, boolean forceListBased, final Object... iArguments) { + return addCluster(iClusterName, -1, forceListBased, iArguments); + } + + public int addCluster(final String iClusterName, final int iRequestedId, final boolean forceListBased, + final Object... iParameters) { + return networkOperation(new OStorageRemoteOperation() { + @Override + public Integer execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + stateLock.acquireWriteLock(); + try { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_ADD, session); + + network.writeString(iClusterName); + if (network.getSrvProtocolVersion() >= 18) + network.writeShort((short) iRequestedId); + } finally { + endRequest(network); + } + + try { + beginResponse(network, session); + final int clusterId = network.readShort(); + + final OClusterRemote cluster = new OClusterRemote(); + cluster.configure(OStorageRemote.this, clusterId, iClusterName.toLowerCase(Locale.ENGLISH)); + + if (clusters.length <= clusterId) + clusters = Arrays.copyOf(clusters, clusterId + 1); + clusters[cluster.getId()] = cluster; + clusterMap.put(cluster.getName().toLowerCase(Locale.ENGLISH), cluster); + + return clusterId; + } finally { + endResponse(network); + } + } finally { + stateLock.releaseWriteLock(); + } + } + }, "Error on add new cluster"); + } + + public boolean dropCluster(final int iClusterId, final boolean iTruncate) { + return networkOperation(new OStorageRemoteOperation() { + @Override + public Boolean execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + stateLock.acquireWriteLock(); + try { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DATACLUSTER_DROP, session); + + network.writeShort((short) iClusterId); + + } finally { + endRequest(network); + } + + byte result = 0; + try { + beginResponse(network, session); + result = network.readByte(); + } finally { + endResponse(network); + } + + if (result == 1) { + // REMOVE THE CLUSTER LOCALLY + final OCluster cluster = clusters[iClusterId]; + clusters[iClusterId] = null; + clusterMap.remove(cluster.getName()); + if (configuration.clusters.size() > iClusterId) + configuration.dropCluster(iClusterId); // endResponse must be called before this line, which call updateRecord + + return true; + } + return false; + } finally { + stateLock.releaseWriteLock(); + } + } + }, "Error on removing of cluster"); + } + + public void synch() { + } + + public String getPhysicalClusterNameById(final int iClusterId) { + stateLock.acquireReadLock(); + try { + + if (iClusterId >= clusters.length) + return null; + + final OCluster cluster = clusters[iClusterId]; + return cluster != null ? cluster.getName() : null; + + } finally { + stateLock.releaseReadLock(); + } + } + + public int getClusterMap() { + stateLock.acquireReadLock(); + try { + return clusterMap.size(); + } finally { + stateLock.releaseReadLock(); + } + } + + public Collection getClusterInstances() { + stateLock.acquireReadLock(); + try { + + return Arrays.asList(clusters); + + } finally { + stateLock.releaseReadLock(); + } + } + + public OCluster getClusterById(int iClusterId) { + stateLock.acquireReadLock(); + try { + + if (iClusterId == ORID.CLUSTER_ID_INVALID) + // GET THE DEFAULT CLUSTER + iClusterId = defaultClusterId; + + return clusters[iClusterId]; + + } finally { + stateLock.releaseReadLock(); + } + } + + @Override + public long getVersion() { + throw new UnsupportedOperationException("getVersion"); + } + + public ODocument getClusterConfiguration() { + return clusterConfiguration; + } + + /** + * Ends the request and unlock the write lock + */ + public void endRequest(final OChannelBinaryAsynchClient iNetwork) throws IOException { + if (iNetwork == null) + return; + + iNetwork.flush(); + iNetwork.releaseWriteLock(); + + } + + /** + * End response reached: release the channel in the pool to being reused + */ + public void endResponse(final OChannelBinaryAsynchClient iNetwork) throws IOException { + iNetwork.endResponse(); + } + + @Override + public boolean isRemote() { + return true; + } + + public boolean isPermanentRequester() { + return false; + } + + @SuppressWarnings("unchecked") + public void updateClusterConfiguration(final String iConnectedURL, final byte[] obj) { + if (obj == null) + return; + + // TEMPORARY FIX: DISTRIBUTED MODE DOESN'T SUPPORT TREE BONSAI, KEEP ALWAYS EMBEDDED RIDS + OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.setValue(Integer.MAX_VALUE); + + final List members; + synchronized (clusterConfiguration) { + clusterConfiguration.fromStream(obj); + clusterConfiguration.toString(); + members = clusterConfiguration.field("members"); + } + + // UPDATE IT + synchronized (serverURLs) { + if (members != null) { + // ADD CURRENT SERVER AS FIRST + if (iConnectedURL != null) { + addHost(iConnectedURL); + } + + for (ODocument m : members) { + if (m == null) + continue; + + final String nodeStatus = m.field("status"); + + if (m != null && !"OFFLINE".equals(nodeStatus)) { + final Collection> listeners = ((Collection>) m.field("listeners")); + if (listeners != null) + for (Map listener : listeners) { + if (((String) listener.get("protocol")).equals("ONetworkProtocolBinary")) { + String url = (String) listener.get("listen"); + if (!serverURLs.contains(url)) + addHost(url); + } + } + } + } + } + } + } + + public void removeSessions(final String url) { + synchronized (serverURLs) { + serverURLs.remove(url); + } + + for (OStorageRemoteSession session : sessions) { + session.removeServerSession(url + "/" + getName()); + } + } + + @Override + public OCluster getClusterByName(final String iClusterName) { + throw new UnsupportedOperationException("getClusterByName()"); + } + + @Override + public ORecordConflictStrategy getConflictStrategy() { + throw new UnsupportedOperationException("getConflictStrategy"); + } + + @Override + public void setConflictStrategy(final ORecordConflictStrategy iResolver) { + throw new UnsupportedOperationException("setConflictStrategy"); + } + + @Override + public String getURL() { + return OEngineRemote.NAME + ":" + url; + } + + public int getClusters() { + stateLock.acquireReadLock(); + try { + return clusterMap.size(); + } finally { + stateLock.releaseReadLock(); + } + } + + @Override + public String getType() { + return OEngineRemote.NAME; + } + + @Override + public String getUserName() { + final OStorageRemoteSession session = getCurrentSession(); + if (session == null) + return null; + return session.connectionUserName; + } + + protected String reopenRemoteDatabase() throws IOException { + String currentURL = getCurrentServerURL(); + do { + do { + final OChannelBinaryAsynchClient network = getNetwork(currentURL); + try { + OStorageRemoteSession session = getCurrentSession(); + OStorageRemoteNodeSession nodeSession = session.getOrCreateServerSession(network.getServerURL()); + if (nodeSession == null || !nodeSession.isValid()) { + openRemoteDatabase(network); + connectionManager.release(network); + return network.getServerURL(); + } else { + try { + network.writeByte(OChannelBinaryProtocol.REQUEST_DB_REOPEN); + network.writeInt(nodeSession.getSessionId()); + network.writeBytes(nodeSession.getToken()); + } finally { + endRequest(network); + } + + final int sessionId; + + try { + byte[] newToken = network.beginResponse(nodeSession.getSessionId(), true); + sessionId = network.readInt(); + if (newToken != null && newToken.length > 0) { + nodeSession.setSession(sessionId, newToken); + } else { + nodeSession.setSession(sessionId, nodeSession.getToken()); + } + OLogManager.instance().debug(this, "Client connected to %s with session id=%d", network.getServerURL(), sessionId); + return currentURL; + } finally { + endResponse(network); + connectionManager.release(network); + } + } + } catch (OIOException e) { + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + connectionManager.remove(network); + } + + OLogManager.instance().error(this, "Cannot open database with url " + currentURL, e); + } catch (OOfflineNodeException e) { + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + connectionManager.remove(network); + } + + OLogManager.instance().debug(this, "Cannot open database with url " + currentURL, e); + } catch (OSecurityException ex) { + OLogManager.instance().debug(this, "Invalidate token for url=%s", ex, currentURL); + OStorageRemoteSession session = getCurrentSession(); + session.removeServerSession(currentURL); + + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + try { + connectionManager.remove(network); + } catch (Exception e) { + // IGNORE ANY EXCEPTION + OLogManager.instance().debug(this, "Cannot remove connection or database url=" + currentURL, e); + } + } + } catch (OException e) { + connectionManager.release(network); + // PROPAGATE ANY OTHER ORIENTDB EXCEPTION + throw e; + + } catch (Exception e) { + OLogManager.instance().debug(this, "Cannot open database with url " + currentURL, e); + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + try { + connectionManager.remove(network); + } catch (Exception ex) { + // IGNORE ANY EXCEPTION + OLogManager.instance().debug(this, "Cannot remove connection or database url=" + currentURL, e); + } + } + } + } while (connectionManager.getAvailableConnections(currentURL) > 0); + + currentURL = useNewServerURL(currentURL); + + } while (currentURL != null); + + // REFILL ORIGINAL SERVER LIST + parseServerURLs(); + + synchronized (serverURLs) { + throw new OStorageException("Cannot create a connection to remote server address(es): " + serverURLs); + } + } + + protected synchronized void openRemoteDatabase() throws IOException { + final String currentURL = getNextAvailableServerURL(true, getCurrentSession()); + openRemoteDatabase(currentURL); + } + + public void openRemoteDatabase(OChannelBinaryAsynchClient network) throws IOException { + stateLock.acquireWriteLock(); + try { + OStorageRemoteSession session = getCurrentSession(); + OStorageRemoteNodeSession nodeSession = session.getOrCreateServerSession(network.getServerURL()); + try { + network.writeByte(OChannelBinaryProtocol.REQUEST_DB_OPEN); + network.writeInt(nodeSession.getSessionId()); + + // @SINCE 1.0rc8 + sendClientInfo(network, DRIVER_NAME, true, true); + + network.writeString(name); + network.writeString(session.connectionUserName); + network.writeString(session.connectionUserPassword); + + } finally { + endRequest(network); + } + + final int sessionId; + + try { + network.beginResponse(nodeSession.getSessionId(), false); + sessionId = network.readInt(); + byte[] token = network.readBytes(); + if (token.length == 0) { + token = null; + } + + nodeSession.setSession(sessionId, token); + + OLogManager.instance().debug(this, "Client connected to %s with session id=%d", network.getServerURL(), sessionId); + + readDatabaseInformation(network); + + // READ CLUSTER CONFIGURATION + updateClusterConfiguration(network.getServerURL(), network.readBytes()); + + // read OrientDB release info + if (network.getSrvProtocolVersion() >= 14) + network.readString(); + + status = STATUS.OPEN; + } finally { + endResponse(network); + } + } finally { + stateLock.releaseWriteLock(); + } + } + + protected void openRemoteDatabase(String currentURL) { + do { + do { + OChannelBinaryAsynchClient network = null; + try { + network = getNetwork(currentURL); + openRemoteDatabase(network); + final int serverVersion = network.getSrvProtocolVersion(); + + connectionManager.release(network); + return; + } catch (OIOException e) { + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + connectionManager.remove(network); + } + + OLogManager.instance().debug(this, "Cannot open database with url " + currentURL, e); + + } catch (OException e) { + connectionManager.release(network); + // PROPAGATE ANY OTHER ORIENTDB EXCEPTION + throw e; + + } catch (Exception e) { + if (network != null) { + // REMOVE THE NETWORK CONNECTION IF ANY + try { + connectionManager.remove(network); + } catch (Exception ex) { + // IGNORE ANY EXCEPTION + OLogManager.instance().debug(this, "Cannot remove connection or database url=" + currentURL, e); + } + } + + OLogManager.instance().error(this, "Cannot open database url=" + currentURL, e); + } + } while (connectionManager.getReusableConnections(currentURL) > 0); + + currentURL = useNewServerURL(currentURL); + + } while (currentURL != null); + + // REFILL ORIGINAL SERVER LIST + parseServerURLs(); + + synchronized (serverURLs) { + throw new OStorageException("Cannot create a connection to remote server address(es): " + serverURLs); + } + } + + protected String useNewServerURL(final String iUrl) { + int pos = iUrl.indexOf('/'); + if (pos >= iUrl.length() - 1) + // IGNORE ENDING / + pos = -1; + + final String postFix = pos > -1 ? iUrl.substring(pos) : ""; + final String url = pos > -1 ? iUrl.substring(0, pos) : iUrl; + + synchronized (serverURLs) { + // REMOVE INVALID URL + serverURLs.remove(url); + for (OStorageRemoteSession activeSession : sessions) { + // Not thread Safe ... + activeSession.removeServerSession(url + "/" + getName()); + } + + OLogManager.instance().debug(this, "Updated server list: %s...", serverURLs); + + if (!serverURLs.isEmpty()) + return serverURLs.get(0) + postFix; + } + + return null; + } + + protected void sendClientInfo(final OChannelBinaryAsynchClient network, final String driverName, + final boolean supportsPushMessages, final boolean collectStats) throws IOException { + if (network.getSrvProtocolVersion() >= 7) { + // @COMPATIBILITY 1.0rc8 + network.writeString(driverName).writeString(OConstants.ORIENT_VERSION) + .writeShort((short) OChannelBinaryProtocol.CURRENT_PROTOCOL_VERSION).writeString(clientId); + } + if (network.getSrvProtocolVersion() > OChannelBinaryProtocol.PROTOCOL_VERSION_21) { + network.writeString(ODatabaseDocumentTx.getDefaultSerializer().toString()); + recordFormat = ODatabaseDocumentTx.getDefaultSerializer().toString(); + } else + recordFormat = ORecordSerializerSchemaAware2CSV.NAME; + if (network.getSrvProtocolVersion() > OChannelBinaryProtocol.PROTOCOL_VERSION_26) + network.writeBoolean(true); + if (network.getSrvProtocolVersion() > OChannelBinaryProtocol.PROTOCOL_VERSION_33) { + network.writeBoolean(supportsPushMessages); + network.writeBoolean(collectStats); + } + } + + /** + * Parse the URLs. Multiple URLs must be separated by semicolon (;) + */ + protected void parseServerURLs() { + String lastHost = null; + int dbPos = url.indexOf('/'); + if (dbPos == -1) { + // SHORT FORM + addHost(url); + lastHost = url; + name = url; + } else { + name = url.substring(url.lastIndexOf("/") + 1); + for (String host : url.substring(0, dbPos).split(ADDRESS_SEPARATOR)) { + lastHost = host; + addHost(host); + } + } + + synchronized (serverURLs) { + if (serverURLs.size() == 1 && OGlobalConfiguration.NETWORK_BINARY_DNS_LOADBALANCING_ENABLED.getValueAsBoolean()) { + // LOOK FOR LOAD BALANCING DNS TXT RECORD + final String primaryServer = lastHost; + + OLogManager.instance().debug(this, "Retrieving URLs from DNS '%s' (timeout=%d)...", primaryServer, + OGlobalConfiguration.NETWORK_BINARY_DNS_LOADBALANCING_TIMEOUT.getValueAsInteger()); + + try { + final Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); + env.put("com.sun.jndi.ldap.connect.timeout", + OGlobalConfiguration.NETWORK_BINARY_DNS_LOADBALANCING_TIMEOUT.getValueAsString()); + final DirContext ictx = new InitialDirContext(env); + final String hostName = !primaryServer.contains(":") ? + primaryServer : + primaryServer.substring(0, primaryServer.indexOf(":")); + final Attributes attrs = ictx.getAttributes(hostName, new String[] { "TXT" }); + final Attribute attr = attrs.get("TXT"); + if (attr != null) { + for (int i = 0; i < attr.size(); ++i) { + String configuration = (String) attr.get(i); + if (configuration.startsWith("\"")) + configuration = configuration.substring(1, configuration.length() - 1); + if (configuration != null) { + serverURLs.clear(); + final String[] parts = configuration.split(" "); + for (String part : parts) { + if (part.startsWith("s=")) { + addHost(part.substring("s=".length())); + } + } + } + } + } + } catch (NamingException ignore) { + } + } + } + } + + /** + * Registers the remote server with port. + */ + protected String addHost(String host) { + if (host.startsWith(LOCALHOST)) + host = LOCAL_IP + host.substring("localhost".length()); + + if (host.contains("/")) + host = host.substring(0, host.indexOf("/")); + + // REGISTER THE REMOTE SERVER+PORT + if (!host.contains(":")) + host += ":" + (clientConfiguration.getValueAsBoolean(OGlobalConfiguration.CLIENT_USE_SSL) ? + getDefaultSSLPort() : + getDefaultPort()); + else if (host.split(":").length < 2 || host.split(":")[1].trim().length() == 0) + host += (clientConfiguration.getValueAsBoolean(OGlobalConfiguration.CLIENT_USE_SSL) ? getDefaultSSLPort() : getDefaultPort()); + + // DISABLED BECAUSE THIS DID NOT ALLOW TO CONNECT TO LOCAL HOST ANYMORE IF THE SERVER IS BOUND TO 127.0.0.1 + // CONVERT 127.0.0.1 TO THE PUBLIC IP IF POSSIBLE + // if (host.startsWith(LOCAL_IP)) { + // try { + // final String publicIP = InetAddress.getLocalHost().getHostAddress(); + // host = publicIP + host.substring(LOCAL_IP.length()); + // } catch (UnknownHostException e) { + // // IGNORE IT + // } + // } + + synchronized (serverURLs) { + if (!serverURLs.contains(host)) { + serverURLs.add(host); + OLogManager.instance().debug(this, "Registered the new available server '%s'", host); + } + } + + return host; + } + + protected int getDefaultPort() { + return DEFAULT_PORT; + } + + protected int getDefaultSSLPort() { + return DEFAULT_SSL_PORT; + } + + /** + * Acquire a network channel from the pool. Don't lock the write stream since the connection usage is exclusive. + * + * @param iCommand id. Ids described at {@link OChannelBinaryProtocol} + * @param session + * + * @return connection to server + * + * @throws IOException + */ + public OChannelBinaryAsynchClient beginRequest(final OChannelBinaryAsynchClient network, final byte iCommand, + OStorageRemoteSession session) throws IOException { + network.beginRequest(iCommand, session); + return network; + } + + protected String getNextAvailableServerURL(boolean iIsConnectOperation, OStorageRemoteSession session) { + String url = null; + switch (connectionStrategy) { + case STICKY: + url = session != null ? session.getServerUrl() : null; + if (url == null) + url = getServerURFromList(false, session); + break; + + case ROUND_ROBIN_CONNECT: + if (!iIsConnectOperation) + url = session != null ? session.getServerUrl() : null; + + if (url == null) + url = getServerURFromList(iIsConnectOperation, session); + OLogManager.instance() + .debug(this, "ROUND_ROBIN_CONNECT: Next remote operation will be executed on server: %s (isConnectOperation=%s)", url, + iIsConnectOperation); + break; + + case ROUND_ROBIN_REQUEST: + url = getServerURFromList(true, session); + OLogManager.instance() + .debug(this, "ROUND_ROBIN_REQUEST: Next remote operation will be executed on server: %s (isConnectOperation=%s)", url, + iIsConnectOperation); + break; + + default: + throw new OConfigurationException("Connection mode " + connectionStrategy + " is not supported"); + } + + return url; + } + + protected String getCurrentServerURL() { + return getServerURFromList(false, getCurrentSession()); + } + + protected String getServerURFromList(final boolean iNextAvailable, OStorageRemoteSession session) { + synchronized (serverURLs) { + if (serverURLs.isEmpty()) { + parseServerURLs(); + if (serverURLs.isEmpty()) + throw new OStorageException("Cannot create a connection to remote server because url list is empty"); + } + + // GET CURRENT THREAD INDEX + int serverURLIndex; + if (session != null) + serverURLIndex = session.serverURLIndex; + else + serverURLIndex = 0; + + if (iNextAvailable) + serverURLIndex++; + + if (serverURLIndex < 0 || serverURLIndex >= serverURLs.size()) + // RESET INDEX + serverURLIndex = 0; + + final String serverURL = serverURLs.get(serverURLIndex) + "/" + getName(); + + if (session != null) + session.serverURLIndex = serverURLIndex; + + return serverURL; + } + } + + public OChannelBinaryAsynchClient getNetwork(final String iCurrentURL) { + OChannelBinaryAsynchClient network; + do { + try { + network = connectionManager.acquire(iCurrentURL, clientConfiguration, connectionOptions, asynchEventListener); + } catch (OIOException cause) { + throw cause; + } catch (Exception cause) { + throw OException.wrapException(new OStorageException("Cannot open a connection to remote server: " + iCurrentURL), cause); + } + if (!network.tryLock()) { + // CANNOT LOCK IT, MAYBE HASN'T BE CORRECTLY UNLOCKED BY PREVIOUS USER? + OLogManager.instance() + .error(this, "Removing locked network channel '%s' (connected=%s)...", iCurrentURL, network.isConnected()); + connectionManager.remove(network); + network = null; + } + } while (network == null); + return network; + } + + public void beginResponse(OChannelBinaryAsynchClient iNetwork, OStorageRemoteSession session) throws IOException { + OStorageRemoteNodeSession nodeSession = session.getServerSession(iNetwork.getServerURL()); + byte[] newToken = iNetwork.beginResponse(nodeSession.getSessionId(), true); + if (newToken != null && newToken.length > 0) { + nodeSession.setSession(nodeSession.getSessionId(), newToken); + } + } + + protected void getResponse(final OChannelBinaryAsynchClient iNetwork, OStorageRemoteSession session) throws IOException { + try { + beginResponse(iNetwork, session); + } finally { + endResponse(iNetwork); + } + } + + private OPhysicalPosition[] readPhysicalPositions(OChannelBinaryAsynchClient network, int positionsCount) throws IOException { + final OPhysicalPosition[] physicalPositions = new OPhysicalPosition[positionsCount]; + + for (int i = 0; i < physicalPositions.length; i++) { + final OPhysicalPosition position = new OPhysicalPosition(); + + position.clusterPosition = network.readLong(); + position.recordSize = network.readInt(); + position.recordVersion = network.readVersion(); + + physicalPositions[i] = position; + } + return physicalPositions; + } + + private Map> readCollectionChanges(final OChannelBinaryAsynchClient network) + throws IOException { + + final int count = network.readInt(); + + final Map> changes = new HashMap>( + count); + for (int i = 0; i < count; i++) { + final long mBitsOfId = network.readLong(); + final long lBitsOfId = network.readLong(); + + final OBonsaiCollectionPointer pointer = OCollectionNetworkSerializer.INSTANCE.readCollectionPointer(network); + changes.put(pointer, new OPair(mBitsOfId, lBitsOfId)); + } + return changes; + } + + private void updateCollection(final Map> changes, + final OSBTreeCollectionManager collectionManager) throws IOException { + + if (collectionManager == null) + return; + + for (Map.Entry> entry : changes.entrySet()) { + final OBonsaiCollectionPointer pointer = entry.getKey(); + final long mBitsOfId = entry.getValue().getKey(); + final long lBitsOfId = entry.getValue().getValue(); + + collectionManager.updateCollectionPointer(new UUID(mBitsOfId, lBitsOfId), pointer); + } + + if (ORecordSerializationContext.getDepth() <= 1) + collectionManager.clearPendingCollections(); + } + + private void commitEntry(final OChannelBinaryAsynchClient iNetwork, final ORecordOperation txEntry) throws IOException { + if (txEntry.type == ORecordOperation.LOADED) + // JUMP LOADED OBJECTS + return; + + // SERIALIZE THE RECORD IF NEEDED. THIS IS DONE HERE TO CATCH EXCEPTION AND SEND A -1 AS ERROR TO THE SERVER TO SIGNAL THE ABORT + // OF TX COMMIT + byte[] stream = null; + try { + switch (txEntry.type) { + case ORecordOperation.CREATED: + case ORecordOperation.UPDATED: + stream = txEntry.getRecord().toStream(); + break; + } + } catch (Exception e) { + // ABORT TX COMMIT + iNetwork.writeByte((byte) -1); + throw OException.wrapException(new OTransactionException("Error on transaction commit"), e); + } + + iNetwork.writeByte((byte) 1); + iNetwork.writeByte(txEntry.type); + iNetwork.writeRID(txEntry.getRecord().getIdentity()); + iNetwork.writeByte(ORecordInternal.getRecordType(txEntry.getRecord())); + + switch (txEntry.type) { + case ORecordOperation.CREATED: + iNetwork.writeBytes(stream); + break; + + case ORecordOperation.UPDATED: + iNetwork.writeVersion(txEntry.getRecord().getVersion()); + iNetwork.writeBytes(stream); + if (iNetwork.getSrvProtocolVersion() >= 23) + iNetwork.writeBoolean(ORecordInternal.isContentChanged(txEntry.getRecord())); + break; + + case ORecordOperation.DELETED: + iNetwork.writeVersion(txEntry.getRecord().getVersion()); + break; + } + } + + private boolean handleDBFreeze() { + boolean retry; + OLogManager.instance().warn(this, + "DB is frozen will wait for " + OGlobalConfiguration.CLIENT_DB_RELEASE_WAIT_TIMEOUT.getValue() + " ms. and then retry."); + retry = true; + try { + Thread.sleep(OGlobalConfiguration.CLIENT_DB_RELEASE_WAIT_TIMEOUT.getValueAsInteger()); + } catch (InterruptedException ie) { + retry = false; + + Thread.currentThread().interrupt(); + } + return retry; + } + + private void readDatabaseInformation(final OChannelBinaryAsynchClient network) throws IOException { + // @COMPATIBILITY 1.0rc8 + final int tot = network.getSrvProtocolVersion() >= 7 ? network.readShort() : network.readInt(); + + stateLock.acquireWriteLock(); + try { + + clusters = new OCluster[tot]; + clusterMap.clear(); + + for (int i = 0; i < tot; ++i) { + final OClusterRemote cluster = new OClusterRemote(); + String clusterName = network.readString(); + final int clusterId = network.readShort(); + if (clusterName != null) { + clusterName = clusterName.toLowerCase(Locale.ENGLISH); + + if (network.getSrvProtocolVersion() < 24) + network.readString(); + + final int dataSegmentId = + network.getSrvProtocolVersion() >= 12 && network.getSrvProtocolVersion() < 24 ? (int) network.readShort() : 0; + + cluster.configure(this, clusterId, clusterName); + + if (clusterId >= clusters.length) + clusters = Arrays.copyOf(clusters, clusterId + 1); + clusters[clusterId] = cluster; + clusterMap.put(clusterName, cluster); + } + } + + final OCluster defaultCluster = clusterMap.get(CLUSTER_DEFAULT_NAME); + if (defaultCluster != null) + defaultClusterId = clusterMap.get(CLUSTER_DEFAULT_NAME).getId(); + + } finally { + stateLock.releaseWriteLock(); + } + } + + private boolean deleteRecord(byte command, final ORecordId iRid, final int iVersion, int iMode, + final ORecordCallback iCallback, final OChannelBinaryAsynchClient network, final OStorageRemoteSession session) + throws IOException { + try { + beginRequest(network, command, session); + network.writeRID(iRid); + network.writeVersion(iVersion); + network.writeByte((byte) iMode); + + } finally { + endRequest(network); + } + + switch (iMode) { + case 0: + // SYNCHRONOUS + try { + beginResponse(network, session); + return network.readByte() == 1; + } finally { + endResponse(network); + } + + case 1: + // ASYNCHRONOUS + if (iCallback != null) { + Callable response = new Callable() { + public Object call() throws Exception { + Boolean result; + + try { + beginResponse(network, session); + result = network.readByte() == 1; + } finally { + endResponse(network); + } + + iCallback.call(iRid, result); + return null; + } + }; + asynchExecutor.submit(new FutureTask(response)); + } + } + return false; + } + + protected OStorageRemoteSession getCurrentSession() { + final ODatabaseDocumentTx db = (ODatabaseDocumentTx) ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db == null) + return null; + OStorageRemoteSession session = (OStorageRemoteSession) ODatabaseDocumentTxInternal.getSessionMetadata(db); + if (session == null) { + session = new OStorageRemoteSession(sessionSerialId.decrementAndGet()); + sessions.add(session); + ODatabaseDocumentTxInternal.setSessionMetadata(db, session); + } + return session; + } + + @Override + public boolean isClosed() { + if (super.isClosed()) + return true; + final OStorageRemoteSession session = getCurrentSession(); + if (session == null) + return false; + return session.isClosed(); + } + + @Override + public OStorageRemote copy(final ODatabaseDocumentTx source, final ODatabaseDocumentTx dest) { + ODatabaseDocumentInternal origin = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + + final OStorageRemoteSession session = (OStorageRemoteSession) ODatabaseDocumentTxInternal.getSessionMetadata(source); + if (session != null) { + // TODO:may run a session reopen + final OStorageRemoteSession newSession = new OStorageRemoteSession(sessionSerialId.decrementAndGet()); + newSession.connectionUserName = session.connectionUserName; + newSession.connectionUserPassword = session.connectionUserPassword; + ODatabaseDocumentTxInternal.setSessionMetadata(dest, newSession); + } + try { + dest.activateOnCurrentThread(); + openRemoteDatabase(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + ODatabaseRecordThreadLocal.INSTANCE.set(origin); + } + return this; + } + + public void importDatabase(final String options, final InputStream inputStream, final String name, + final OCommandOutputListener listener) { + networkOperationRetry(new OStorageRemoteOperation() { + @Override + public Void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + beginRequest(network, OChannelBinaryProtocol.REQUEST_DB_IMPORT, session); + network.writeString(options); + network.writeString(name); + byte[] buffer = new byte[1024]; + int size; + while ((size = inputStream.read(buffer)) > 0) { + network.writeBytes(buffer, size); + } + network.writeBytes(null); + } finally { + endRequest(network); + } + + int timeout = network.getSocketTimeout(); + try { + // Import messages are sent while import is running, using the request timeout instead of message timeout to avoid early + // reading interrupt. + network.setSocketTimeout(OGlobalConfiguration.NETWORK_REQUEST_TIMEOUT.getValueAsInteger()); + beginResponse(network, session); + String message; + while ((message = network.readString()) != null) { + listener.onMessage(message); + } + } finally { + endResponse(network); + network.setSocketTimeout(timeout); + } + return null; + } + }, "Error sending import request", 0); + } + +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsynchEventListener.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsynchEventListener.java new file mode 100755 index 00000000000..8ccf30aaa46 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsynchEventListener.java @@ -0,0 +1,161 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.sql.query.OLiveResultListener; +import com.orientechnologies.orient.core.storage.OStorage.STATUS; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import com.orientechnologies.orient.enterprise.channel.binary.ORemoteServerEventListener; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class OStorageRemoteAsynchEventListener implements ORemoteServerEventListener { + + private Map liveQueryListeners = new ConcurrentHashMap(); + private ConcurrentMap> poolLiveQuery = new ConcurrentHashMap>(); + + private OStorageRemote storage; + + public OStorageRemoteAsynchEventListener(final OStorageRemote storage) { + this.storage = storage; + } + + public void onRequest(final byte iRequestCode, final Object obj) { + //Using get status to avoid to check the session. + if(storage.getStatus() == STATUS.CLOSED) + return; + + if (iRequestCode == OChannelBinaryProtocol.REQUEST_PUSH_DISTRIB_CONFIG) { + storage.updateClusterConfiguration(null, (byte[]) obj); + + if (OLogManager.instance().isDebugEnabled()) { + synchronized (storage.getClusterConfiguration()) { + OLogManager.instance().debug(this, "Received new cluster configuration: %s", storage.getClusterConfiguration().toJSON("prettyPrint")); + } + } + } else if (iRequestCode == OChannelBinaryProtocol.REQUEST_PUSH_LIVE_QUERY) { + byte[] bytes = (byte[]) obj; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + Integer id = null; + try { + byte what = dis.readByte(); + if (what == 'r') { + byte op = dis.readByte(); + id = dis.readInt(); + + final ORecord record = Orient.instance().getRecordFactoryManager().newInstance(dis.readByte()); + + final int version = readVersion(dis); + final ORecordId rid = readRID(dis); + final byte[] content = readBytes(dis); + ORecordInternal.fill(record, rid, version, content, false); + + OLiveResultListener listener = liveQueryListeners.get(id); + if (listener != null) { + listener.onLiveResult(id, new ORecordOperation(record, op)); + } else { + OLogManager.instance().warn(this, "Receiving invalid LiveQuery token: " + id); + } + } else if (what == 'u') { + id = dis.readInt(); + OLiveResultListener listener = liveQueryListeners.get(id); + listener.onUnsubscribe(id); + } + + } catch (IOException e) { + if (id != null) { + OLiveResultListener listener = liveQueryListeners.get(id); + if (listener != null) { + listener.onError(id); + } + } + e.printStackTrace(); + } + + } + byte op; + + } + + private int readVersion(DataInputStream dis) throws IOException { + return dis.readInt(); + } + + private ORecordId readRID(DataInputStream dis) throws IOException { + final int clusterId = dis.readShort(); + final long clusterPosition = dis.readLong(); + return new ORecordId(clusterId, clusterPosition); + } + + public byte[] readBytes(DataInputStream in) throws IOException { + // TODO see OChannelBinary + final int len = in.readInt(); + if (len < 0) + return null; + final byte[] tmp = new byte[len]; + in.readFully(tmp); + return tmp; + } + + public OStorageRemote getStorage() { + return storage; + } + + public void registerLiveListener(ORemoteConnectionPool pool, Integer id, OLiveResultListener listener) { + this.liveQueryListeners.put(id, listener); + Set res = this.poolLiveQuery.get(pool); + if (res == null) { + res = Collections.synchronizedSet(new HashSet()); + Set prev = poolLiveQuery.putIfAbsent(pool, res); + if (prev != null) + res = prev; + } + res.add(id); + } + + public void unregisterLiveListener(Integer id) { + this.liveQueryListeners.remove(id); + } + + public void onEndUsedConnections(ORemoteConnectionPool pool) { + final Set res = this.poolLiveQuery.get(pool); + if (res != null) + for (Integer query : res) { + OLiveResultListener liveQuery = this.liveQueryListeners.remove(query); + liveQuery.onError(query); + } + + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteConfiguration.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteConfiguration.java new file mode 100755 index 00000000000..4d59293bb9d --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteConfiguration.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.config.OStorageConfiguration; + +import java.nio.charset.Charset; + +public class OStorageRemoteConfiguration extends OStorageConfiguration { + + private static final long serialVersionUID = -3850696054909943272L; + private String networkRecordSerializer; + + public OStorageRemoteConfiguration(OStorageRemote oStorageRemote, String iRecordSerializer) { + super(oStorageRemote, Charset.forName("UTF-8")); + networkRecordSerializer = iRecordSerializer; + } + + @Override + public String getRecordSerializer() { + return networkRecordSerializer; + } + + @Override + public int getRecordSerializerVersion() { + return super.getRecordSerializerVersion(); + } + +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteNodeSession.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteNodeSession.java new file mode 100644 index 00000000000..b7c724852ba --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteNodeSession.java @@ -0,0 +1,36 @@ +package com.orientechnologies.orient.client.remote; + +/** + * Created by tglman on 12/04/16. + */ +public class OStorageRemoteNodeSession { + private final String serverURL; + private Integer sessionId = -1; + private byte[] token = null; + + public OStorageRemoteNodeSession(String serverURL, Integer uniqueClientSessionId) { + this.serverURL = serverURL; + this.sessionId = uniqueClientSessionId; + } + + public String getServerURL() { + return serverURL; + } + + public Integer getSessionId() { + return sessionId; + } + + public byte[] getToken() { + return token; + } + + public void setSession(Integer sessionId, byte[] token) { + this.sessionId = sessionId; + this.token = token; + } + + public boolean isValid() { + return this.sessionId >= 0; + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperation.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperation.java new file mode 100644 index 00000000000..a7752c6f8a1 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperation.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import java.io.IOException; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; + +/** + * Created by tglman on 16/12/15. + */ +public interface OStorageRemoteOperation { + + T execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException; + +} + diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationRead.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationRead.java new file mode 100644 index 00000000000..7f1cc7a3e72 --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationRead.java @@ -0,0 +1,13 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; + +import java.io.IOException; + +/** + * Created by tglman on 07/06/16. + */ +public interface OStorageRemoteOperationRead { + + T execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException; +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationWrite.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationWrite.java new file mode 100644 index 00000000000..3abd323a25a --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteOperationWrite.java @@ -0,0 +1,13 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; + +import java.io.IOException; + +/** + * Created by tglman on 07/06/16. + */ +public interface OStorageRemoteOperationWrite { + + void execute(final OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException; +} diff --git a/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteSession.java b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteSession.java new file mode 100644 index 00000000000..f2107715b9a --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/client/remote/OStorageRemoteSession.java @@ -0,0 +1,101 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.db.ODatabaseSessionMetadata; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary; + +import java.util.*; + +/** + * Created by tglman on 31/03/16. + */ +public class OStorageRemoteSession implements ODatabaseSessionMetadata { + boolean commandExecuting = false; + int serverURLIndex = -1; + String connectionUserName = null; + String connectionUserPassword = null; + Map sessions = new HashMap(); + + private Set connections = Collections + .newSetFromMap(new WeakHashMap()); + private final int uniqueClientSessionId; + private boolean closed = true; + + public OStorageRemoteSession(final int sessionId) { + this.uniqueClientSessionId = sessionId; + } + + public boolean hasConnection(final OChannelBinary connection) { + return connections.contains(connection); + } + + public OStorageRemoteNodeSession getServerSession(final String serverURL) { + return sessions.get(serverURL); + } + + public synchronized OStorageRemoteNodeSession getOrCreateServerSession(final String serverURL) { + OStorageRemoteNodeSession session = sessions.get(serverURL); + if (session == null) { + session = new OStorageRemoteNodeSession(serverURL, uniqueClientSessionId); + sessions.put(serverURL, session); + closed = false; + } + return session; + } + + public void addConnection(final OChannelBinary connection) { + connections.add(connection); + } + + public void close() { + commandExecuting = false; + serverURLIndex = -1; + connections = new HashSet(); + sessions = new HashMap(); + closed = true; + } + + public boolean isClosed() { + return closed; + } + + public Integer getSessionId() { + if (sessions.isEmpty()) + return -1; + final OStorageRemoteNodeSession curSession = sessions.values().iterator().next(); + return curSession.getSessionId(); + } + + public String getServerUrl() { + if (sessions.isEmpty()) + return null; + final OStorageRemoteNodeSession curSession = sessions.values().iterator().next(); + return curSession.getServerURL(); + } + + public synchronized void removeServerSession(final String serverURL) { + sessions.remove(serverURL); + } + + public synchronized Collection getAllServerSessions() { + return sessions.values(); + } +} diff --git a/client/src/main/java/com/orientechnologies/orient/core/db/OrientDBRemote.java b/client/src/main/java/com/orientechnologies/orient/core/db/OrientDBRemote.java new file mode 100755 index 00000000000..c8fcf1e8cff --- /dev/null +++ b/client/src/main/java/com/orientechnologies/orient/core/db/OrientDBRemote.java @@ -0,0 +1,364 @@ +/* + * + * * Copyright 2010-2016 OrientDB LTD (http://orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ + +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.client.remote.ORemoteConnectionManager; +import com.orientechnologies.orient.client.remote.OServerAdmin; +import com.orientechnologies.orient.client.remote.OStorageRemote; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentRemote; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.concurrent.Callable; + +import static com.orientechnologies.orient.client.remote.OStorageRemote.ADDRESS_SEPARATOR; +import static com.orientechnologies.orient.core.config.OGlobalConfiguration.NETWORK_LOCK_TIMEOUT; +import static com.orientechnologies.orient.core.config.OGlobalConfiguration.NETWORK_SOCKET_RETRY; + +/** + * Created by tglman on 08/04/16. + */ +public class OrientDBRemote implements OrientDBInternal { + private final Map storages = new HashMap<>(); + private final Set pools = new HashSet<>(); + private final String[] hosts; + private final OrientDBConfig configurations; + private final Orient orient; + protected volatile ORemoteConnectionManager connectionManager; + private volatile boolean open = true; + private Timer timer; + + public OrientDBRemote(String[] hosts, OrientDBConfig configurations, Orient orient) { + super(); + timer = new Timer(); + this.hosts = hosts; + this.orient = orient; + this.configurations = configurations != null ? configurations : OrientDBConfig.defaultConfig(); + connectionManager = new ORemoteConnectionManager(this.configurations.getConfigurations(), timer); + orient.addOrientDB(this); + } + + private String buildUrl(String name) { + return String.join(ADDRESS_SEPARATOR, hosts) + "/" + name; + } + + public ODatabaseDocumentInternal open(String name, String user, String password) { + return open(name, user, password, null); + } + + @Override + public synchronized ODatabaseDocumentInternal open(String name, String user, String password, OrientDBConfig config) { + checkOpen(); + OrientDBConfig resolvedConfig = solveConfig(config); + try { + OStorageRemote storage; + storage = storages.get(name); + if (storage == null) { + storage = new OStorageRemote(buildUrl(name), this, "rw", connectionManager, resolvedConfig); + storages.put(name, storage); + } + ODatabaseDocumentRemote db = new ODatabaseDocumentRemote(storage); + db.internalOpen(user, password, resolvedConfig); + return db; + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Cannot open database '" + name + "'"), e); + } + } + + @Override + public void create(String name, String user, String password, ODatabaseType databaseType) { + create(name, user, password, databaseType, null); + } + + @Override + public synchronized void create(String name, String user, String password, ODatabaseType databaseType, OrientDBConfig config) { + connectEndExecute(name, user, password, admin -> { + String sendType = null; + if (databaseType == ODatabaseType.MEMORY) { + sendType = "memory"; + } else if (databaseType == ODatabaseType.PLOCAL) { + sendType = "plocal"; + } + admin.createDatabase(name, null, sendType); + return null; + }); + } + + public synchronized ODatabaseDocumentRemotePooled poolOpen(String name, String user, String password, + ODatabasePoolInternal pool) { + OStorageRemote storage = storages.get(name); + if (storage == null) { + try { + storage = new OStorageRemote(buildUrl(name), this, "rw", connectionManager, solveConfig(pool.getConfig())); + storages.put(name, storage); + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Cannot open database '" + name + "'"), e); + } + } + ODatabaseDocumentRemotePooled db = new ODatabaseDocumentRemotePooled(pool, storage); + db.internalOpen(user, password, pool.getConfig()); + return db; + } + + public synchronized void closeStorage(OStorageRemote remote) { + ODatabaseDocumentRemote.deInit(remote); + storages.remove(remote.getName()); + remote.shutdown(); + } + + public ODocument getServerInfo(String username, String password) { + return connectEndExecute(null, username, password, (admin) -> { + return admin.getServerInfo(); + }); + } + + public ODocument getClusterStatus(String username, String password) { + return connectEndExecute(null, username, password, (admin) -> { + return admin.clusterStatus(); + }); + } + + public String getGlobalConfiguration(String username, String password, OGlobalConfiguration config) { + return connectEndExecute(null, username, password, (admin) -> { + return admin.getGlobalConfiguration(config); + }); + } + + public void setGlobalConfiguration(String username, String password, OGlobalConfiguration config, String iConfigValue) { + connectEndExecute(null, username, password, (admin) -> { + admin.setGlobalConfiguration(config, iConfigValue); + return null; + }); + } + + public Map getGlobalConfigurations(String username, String password) { + return connectEndExecute(null, username, password, (admin) -> { + return admin.getGlobalConfigurations(); + }); + } + + public ORemoteConnectionManager getConnectionManager() { + return connectionManager; + } + + private interface Operation { + T execute(OServerAdmin admin) throws IOException; + } + + private T connectEndExecute(String name, String user, String password, Operation operation) { + checkOpen(); + OServerAdmin admin = null; + int retry = configurations.getConfigurations().getValueAsInteger(NETWORK_SOCKET_RETRY); + while (retry > 0) { + try { + admin = new OServerAdmin(this, buildUrl(name)); + admin.connect(user, password); + return operation.execute(admin); + } catch (IOException e) { + retry--; + if (retry == 0) + throw OException + .wrapException(new ODatabaseException("Reached maximum retry limit on admin operations, the server may be offline"), + e); + } finally { + if (admin != null) + admin.close(); + } + } + // SHOULD NEVER REACH THIS POINT + throw new ODatabaseException("Reached maximum retry limit on admin operations, the server may be offline"); + } + + @Override + public synchronized boolean exists(String name, String user, String password) { + return connectEndExecute(name, user, password, admin -> { + // TODO: check for memory cases + return admin.existsDatabase(name, null); + }); + } + + @Override + public synchronized void drop(String name, String user, String password) { + connectEndExecute(name, user, password, admin -> { + // TODO: check for memory cases + return admin.dropDatabase(name, null); + }); + } + + @Override + public Set listDatabases(String user, String password) { + return connectEndExecute("", user, password, admin -> { + // TODO: check for memory cases + return admin.listDatabases().keySet(); + }); + } + + @Override + public void restore(String name, String user, String password, ODatabaseType type, String path, OrientDBConfig config) { + connectEndExecute(name, user, password, admin -> { + admin.createDatabase(name, "", type.name().toLowerCase(), path).close(); + return null; + }); + + } + + public ODatabasePoolInternal openPool(String name, String user, String password) { + return openPool(name, user, password, null); + } + + @Override + public ODatabasePoolInternal openPool(String name, String user, String password, OrientDBConfig config) { + checkOpen(); + ODatabasePoolImpl pool = new ODatabasePoolImpl(this, name, user, password, solveConfig(config)); + pools.add(pool); + return pool; + } + + public void removePool(ODatabasePoolInternal pool) { + pools.remove(pool); + } + + @Override + public void close() { + if (!open) + return; + removeShutdownHook(); + internalClose(); + } + + public void internalClose() { + if (!open) + return; + final List storagesCopy; + synchronized (this) { + // SHUTDOWN ENGINES AVOID OTHER OPENS + open = false; + storagesCopy = new ArrayList<>(storages.values()); + } + + for (OStorageRemote stg : storagesCopy) { + try { + ODatabaseDocumentRemote.deInit(stg); + OLogManager.instance().info(this, "- shutdown storage: " + stg.getName() + "..."); + stg.shutdown(); + } catch (Exception e) { + OLogManager.instance().warn(this, "-- error on shutdown storage", e); + } catch (Error e) { + OLogManager.instance().warn(this, "-- error on shutdown storage", e); + throw e; + } + } + synchronized (this) { + storages.clear(); + + connectionManager.close(); + } + } + + private OrientDBConfig solveConfig(OrientDBConfig config) { + if (config != null) { + config.setParent(this.configurations); + return config; + } else { + OrientDBConfig cfg = OrientDBConfig.defaultConfig(); + cfg.setParent(this.configurations); + return cfg; + } + } + + private void checkOpen() { + if (!open) + throw new ODatabaseException("OrientDB Instance is closed"); + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public boolean isEmbedded() { + return false; + } + + @Override + public void removeShutdownHook() { + orient.removeOrientDB(this); + } + + @Override + public void loadAllDatabases() { + //In remote does nothing + } + + @Override + public ODatabaseDocumentInternal openNoAuthenticate(String iDbUrl, String user) { + throw new UnsupportedOperationException("Open with no authentication is not supported in remote"); + } + + @Override + public void initCustomStorage(String name, String baseUrl, String userName, String userPassword) { + throw new UnsupportedOperationException("Custom storage is not supported in remote"); + } + + @Override + public Collection getStorages() { + throw new UnsupportedOperationException("List storage is not supported in remote"); + } + + @Override + public void replaceFactory(OEmbeddedDatabaseInstanceFactory instanceFactory) { + throw new UnsupportedOperationException("instance factory is not supported in remote"); + } + + @Override + public synchronized void forceDatabaseClose(String databaseName) { + OStorageRemote remote = storages.get(databaseName); + if (remote != null) + closeStorage(remote); + } + + @Override + public OEmbeddedDatabaseInstanceFactory getFactory() { + throw new UnsupportedOperationException("instance factory is not supported in remote"); + } + + @Override + public void restore(String name, InputStream in, Map options, Callable callable, + OCommandOutputListener iListener) { + throw new UnsupportedOperationException("raw restore is not supported in remote"); + } + + @Override + public ODatabaseDocumentInternal openNoAuthorization(String name) { + throw new UnsupportedOperationException("impossible skip authentication and authorization in remote"); + } + +} diff --git a/client/src/main/resources/META-INF/MANIFEST.MF b/client/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..9c256828519 --- /dev/null +++ b/client/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Class-Path: orient-database-enterprise.jar \ No newline at end of file diff --git a/client/src/main/resources/META-INF/services/com.orientechnologies.orient.core.engine.OEngine b/client/src/main/resources/META-INF/services/com.orientechnologies.orient.core.engine.OEngine new file mode 100644 index 00000000000..9a203b58371 --- /dev/null +++ b/client/src/main/resources/META-INF/services/com.orientechnologies.orient.core.engine.OEngine @@ -0,0 +1,40 @@ +# +# Copyright 2015 OrientDB LTD (info(at)orientdb.com) +# +# Licensed 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. +# +# For more information: http://www.orientdb.com +# + +# +# /* +# * Copyright 2016 OrientDB LTD (info(at)orientdb.com) +# * +# * Licensed 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. +# * +# * For more information: http://www.orientechnologies.com +# */ +# + + +com.orientechnologies.orient.client.remote.OEngineRemote \ No newline at end of file diff --git a/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListenerTest.java b/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListenerTest.java new file mode 100644 index 00000000000..fec1ce5552f --- /dev/null +++ b/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteConnectionPushListenerTest.java @@ -0,0 +1,63 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelListener; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.internal.verification.VerificationModeFactory; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Created by tglman on 22/10/15. + */ +public class ORemoteConnectionPushListenerTest { + + @Test + public void testConnectionPoolListenerPropagate() { + + OChannelBinaryAsynchClient chann = Mockito.mock(OChannelBinaryAsynchClient.class); + OStorageRemoteAsynchEventListener listener = Mockito.mock(OStorageRemoteAsynchEventListener.class); + ORemoteConnectionPool pool = Mockito.mock(ORemoteConnectionPool.class); + ORemoteConnectionPushListener poolListener = new ORemoteConnectionPushListener(); + poolListener.addListener(pool, chann, listener); + poolListener.onRequest((byte) 10, null); + + Mockito.verify(listener, VerificationModeFactory.only()).onRequest(Mockito.anyByte(), Mockito.anyObject()); + } + + @Test + public void testRegistredOnlyOnce() { + OChannelBinaryAsynchClient chann = Mockito.mock(OChannelBinaryAsynchClient.class); + OStorageRemoteAsynchEventListener listener = Mockito.mock(OStorageRemoteAsynchEventListener.class); + ORemoteConnectionPushListener poolListener = new ORemoteConnectionPushListener(); + ORemoteConnectionPool pool = Mockito.mock(ORemoteConnectionPool.class); + poolListener.addListener(pool, chann, listener); + poolListener.addListener(pool, chann, listener); + poolListener.onRequest((byte) 10, null); + + Mockito.verify(listener, VerificationModeFactory.only()).onRequest(Mockito.anyByte(), Mockito.anyObject()); + + } + + @Test + public void testCloseListerner() { + OChannelBinaryAsynchClient chann = Mockito.mock(OChannelBinaryAsynchClient.class); + OStorageRemoteAsynchEventListener listener = Mockito.mock(OStorageRemoteAsynchEventListener.class); + ORemoteConnectionPool pool = Mockito.mock(ORemoteConnectionPool.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(OChannelListener.class); + Mockito.doNothing().when(chann).registerListener(captor.capture()); + + ORemoteConnectionPushListener poolListener = new ORemoteConnectionPushListener(); + poolListener.addListener(pool, chann, listener); + poolListener.addListener(pool, chann, listener); + captor.getValue().onChannelClose(chann); + + Mockito.verify(listener, VerificationModeFactory.only()).onEndUsedConnections(pool); + + } + + +} diff --git a/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteStorageLiveQueryPushListenerTest.java b/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteStorageLiveQueryPushListenerTest.java new file mode 100644 index 00000000000..08b38504ef2 --- /dev/null +++ b/client/src/test/java/com/orientechnologies/orient/client/remote/ORemoteStorageLiveQueryPushListenerTest.java @@ -0,0 +1,37 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.sql.query.OLiveResultListener; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Created by tglman on 26/10/15. + */ +public class ORemoteStorageLiveQueryPushListenerTest { + + + @Mock + private OStorageRemote storage; + @Mock + private ORemoteConnectionPool pool; + @Mock + private OLiveResultListener listener; + + @BeforeMethod + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testErrorOnConectionClose() { + OStorageRemoteAsynchEventListener storageListener = new OStorageRemoteAsynchEventListener(storage); + storageListener.registerLiveListener(pool, 10, listener); + storageListener.onEndUsedConnections(pool); + Mockito.verify(listener, Mockito.only()).onError(10); + } + + +} diff --git a/client/src/test/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemoteTest.java b/client/src/test/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemoteTest.java new file mode 100644 index 00000000000..99aff696000 --- /dev/null +++ b/client/src/test/java/com/orientechnologies/orient/client/remote/OSBTreeCollectionManagerRemoteTest.java @@ -0,0 +1,91 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeCollectionManagerRemoteTest { + + private static final int EXPECTED_FILE_ID = 17; + private static final OBonsaiBucketPointer EXPECTED_ROOT_POINTER = new OBonsaiBucketPointer(11, 118); + private static final int EXPECTED_CLUSTER_ID = 3; + + @Mock + private OCollectionNetworkSerializer networkSerializerMock; + @Mock + private ODatabaseDocumentInternal dbMock; + @Mock + private OStorageRemote storageMock; + @Mock + private OChannelBinaryAsynchClient clientMock; + + @BeforeMethod + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test(enabled = false) + public void testCreateTree() throws Exception { + OSBTreeCollectionManagerRemote remoteManager = new OSBTreeCollectionManagerRemote(storageMock, networkSerializerMock); + ODatabaseRecordThreadLocal.INSTANCE.set(dbMock); + + when(dbMock.getStorage()).thenReturn(storageMock); + when(storageMock.getUnderlying()).thenReturn(storageMock); + when(storageMock + .beginRequest(Mockito.any(OChannelBinaryAsynchClient.class), eq(OChannelBinaryProtocol.REQUEST_CREATE_SBTREE_BONSAI), + Mockito.any(OStorageRemoteSession.class))).thenReturn(clientMock); + when(networkSerializerMock.readCollectionPointer(Mockito.any())) + .thenReturn(new OBonsaiCollectionPointer(EXPECTED_FILE_ID, EXPECTED_ROOT_POINTER)); + + OSBTreeBonsaiRemote tree = remoteManager.createTree(EXPECTED_CLUSTER_ID); + + assertNotNull(tree); + assertEquals(tree.getFileId(), EXPECTED_FILE_ID); + assertEquals(tree.getRootBucketPointer(), EXPECTED_ROOT_POINTER); + + verify(dbMock).getStorage(); + verifyNoMoreInteractions(dbMock); + + verify(storageMock).getUnderlying(); + verify(storageMock) + .beginRequest(Mockito.any(OChannelBinaryAsynchClient.class), eq(OChannelBinaryProtocol.REQUEST_CREATE_SBTREE_BONSAI), + Mockito.any(OStorageRemoteSession.class)); + verify(clientMock).writeInt(eq(EXPECTED_CLUSTER_ID)); + verify(storageMock).endRequest(Matchers.same(clientMock)); + verify(storageMock).beginResponse(Matchers.same(clientMock), Mockito.any(OStorageRemoteSession.class)); + verify(networkSerializerMock).readCollectionPointer(Matchers.same(clientMock)); + verify(storageMock).endResponse(Matchers.same(clientMock)); + verifyNoMoreInteractions(storageMock); + } + + @Test + public void testLoadTree() throws Exception { + OSBTreeCollectionManagerRemote remoteManager = new OSBTreeCollectionManagerRemote(storageMock, networkSerializerMock); + + OSBTreeBonsai tree = remoteManager + .loadTree(new OBonsaiCollectionPointer(EXPECTED_FILE_ID, EXPECTED_ROOT_POINTER)); + + assertNotNull(tree); + assertEquals(tree.getFileId(), EXPECTED_FILE_ID); + assertEquals(tree.getRootBucketPointer(), EXPECTED_ROOT_POINTER); + } +} diff --git a/client/src/test/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsyncOperationTest.java b/client/src/test/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsyncOperationTest.java new file mode 100644 index 00000000000..33b6fae6cf7 --- /dev/null +++ b/client/src/test/java/com/orientechnologies/orient/client/remote/OStorageRemoteAsyncOperationTest.java @@ -0,0 +1,137 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.client.binary.OChannelBinaryAsynchClient; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.storage.ORecordCallback; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * Created by tglman on 09/06/16. + */ +public class OStorageRemoteAsyncOperationTest { + + private OStorageRemote storage; + + @Mock + private OChannelBinaryAsynchClient channel; + + @Mock + private ORemoteConnectionManager connectionManager; + + private class CallStatus { + public String status; + } + + @Before + public void before() throws IOException { + MockitoAnnotations.initMocks(this); + final OStorageRemoteSession session = new OStorageRemoteSession(10); + storage = new OStorageRemote("mock", "mock", "mock") { + @Override + public T baseNetworkOperation(OStorageRemoteOperation operation, String errorMessage, int retry) { + try { + return operation.execute(channel, session); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + storage.connectionManager = connectionManager; + } + + @Test + public void testSyncCall() { + final CallStatus status = new CallStatus(); + storage.asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException { + assertNull(status.status); + status.status = "write"; + } + }, new OStorageRemoteOperationRead() { + @Override + public Object execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + assertEquals(status.status, "write"); + status.status = "read"; + return null; + } + }, 0, new ORecordId(-1, -1), null, ""); + + assertEquals(status.status, "read"); + } + + @Test + public void testNoReadCall() { + final CallStatus status = new CallStatus(); + storage.asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException { + assertNull(status.status); + status.status = "write"; + } + }, new OStorageRemoteOperationRead() { + @Override + public Object execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + fail(); + return null; + } + }, 1, new ORecordId(-1, -1), null, ""); + + assertEquals(status.status, "write"); + } + + @Test + public void testAsyncRead() throws InterruptedException { + final CallStatus status = new CallStatus(); + final CountDownLatch callBackWait = new CountDownLatch(1); + final CountDownLatch readDone = new CountDownLatch(1); + final CountDownLatch callBackDone = new CountDownLatch(1); + final Object res = new Object(); + storage.asyncNetworkOperation(new OStorageRemoteOperationWrite() { + @Override + public void execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session, int mode) throws IOException { + assertNull(status.status); + status.status = "write"; + } + }, new OStorageRemoteOperationRead() { + @Override + public Object execute(OChannelBinaryAsynchClient network, OStorageRemoteSession session) throws IOException { + try { + if (callBackWait.await(10, TimeUnit.MILLISECONDS)) + readDone.countDown(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return res; + } + }, 1, new ORecordId(-1, -1), new ORecordCallback() { + @Override + public void call(ORecordId iRID, Object iParameter) { + callBackDone.countDown(); + } + }, ""); + + // SBLCK THE CALLBAC THAT SHOULD BE IN ANOTHER THREAD + callBackWait.countDown(); + + boolean called = readDone.await(10, TimeUnit.MILLISECONDS); + if (!called) + fail("Read not called"); + called = callBackDone.await(10, TimeUnit.MILLISECONDS); + if (!called) + fail("Callback not called"); + } + +} diff --git a/client/src/test/java/com/orientechnologies/orient/client/remote/RemoteConnetWrongUrlTest.java b/client/src/test/java/com/orientechnologies/orient/client/remote/RemoteConnetWrongUrlTest.java new file mode 100644 index 00000000000..7461193b76d --- /dev/null +++ b/client/src/test/java/com/orientechnologies/orient/client/remote/RemoteConnetWrongUrlTest.java @@ -0,0 +1,17 @@ +package com.orientechnologies.orient.client.remote; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.OStorageException; +import org.testng.annotations.Test; + +public class RemoteConnetWrongUrlTest { + + @Test(expectedExceptions = OStorageException.class) + public void testConnectWrongUrl() { + ODatabaseDocument doc = new ODatabaseDocumentTx("remote:wrong:2424/test"); + doc.open("user", "user"); + + } + +} diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000000..ae3c1726048 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/core/findbugs_filter.xml b/core/findbugs_filter.xml new file mode 100755 index 00000000000..b4393f2db6c --- /dev/null +++ b/core/findbugs_filter.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml new file mode 100755 index 00000000000..b673659c0d9 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,269 @@ + + + + + + + 4.0.0 + + + com.orientechnologies + orientdb-parent + 2.2.24 + ../ + + + orientdb-core + + OrientDB Core + + + true + + com.orientechnologies.orient.graph.console;resolution:=optional, + com.orientechnologies.orient.graph.gremlin;resolution:=optional, + com.orientechnologies.orient.graph.handler;resolution:=optional, + com.orientechnologies.orient.graph.sql.functions;resolution:=optional, + javax.imageio.spi,sun.misc;resolution:=optional, + com.orientechnologies.orient.client.remote;resolution:=optional, + com.sun.jna;resolution:=optional, + org.xerial.snappy;resolution:=optional, + sun.nio.ch;resolution:=optional, + * + + com.orientechnologies.orient.core.*, + com.orientechnologies.common.*,com.orientechnologies.nio.*, + com.orientechnologies.orient.enterprise.* + + 4.0.0 + UTF-8 + -ea -Xmx3072m -XX:MaxDirectMemorySize=512g + -Dstorage.diskCache.bufferSize=4096 + -Dindex.flushAfterCreate=false + -Dstorage.makeFullCheckpointAfterCreate=false + -Dstorage.makeFullCheckpointAfterOpen=false + -Dstorage.makeFullCheckpointAfterClusterCreate=false + -Dstorage.wal.syncOnPageFlush=false + -Dstorage.configuration.syncOnUpdate=false + -Ddb.makeFullCheckpointOnIndexChange=false + -Ddb.makeFullCheckpointOnSchemaChange=false + -Dsecurity.userPasswordSaltIterations=10 + -Dmemory.directMemory.trackMode=true + -Djava.util.logging.manager=com.orientechnologies.common.log.OLogManager$DebugLogManager + -Dstorage.diskCache.checksumMode=storeAndThrow + + + + + + development + + true + + + **/LocalHashTableIterationTest.java + **/OLocalHashTableTest.java + **/SBTreeTest.java + **/SBTreeTestBigValues.java + **/OSBTreeBonsaiLocalTest.java + **/LocalPaginatedClusterTest.java + **/WOWCacheTest.java + **/OLocalHashTableWALTest.java + **/SBTreeWALTest.java + **/LocalPaginatedClusterWithWALTest.java + **/OSBTreeBonsaiWALTest.java + **/InvalidRemovedFileIdsTest.java + + + + + ci + + + orientdb.test.env + ci + + + + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + + + + + release + + + orientdb.test.env + release + + + + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + empty.java + + + + + localDeploy + + + localDeploy + + + + + + + + + + + + + + + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + generate-verion-class + + filter-sources + + + + + + org.codehaus.mojo + javacc-maven-plugin + 2.6 + + + jjtree-javacc + + jjtree-javacc + + + + + ${basedir}/src/main/grammar + ${basedir}/src/main/java + ${basedir}/src/main/java + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + ${project.build.directory} + + + ${exclude.test.1} + ${exclude.test.2} + ${exclude.test.3} + ${exclude.test.4} + ${exclude.test.5} + ${exclude.test.6} + ${exclude.test.7} + ${exclude.test.8} + ${exclude.test.9} + ${exclude.test.10} + ${exclude.test.11} + ${exclude.test.12} + + + + listener + com.orientechnologies.OTestNGTestLeaksListener + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + + + + test-jar + + + + + + + + + + org.xerial.snappy + snappy-java + 1.1.0.1 + + + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + 1.4.1 + + + com.google.code.findbugs + jsr305 + + + + + + com.orientechnologies + orientdb-test-commons + ${project.version} + test + + + + com.google.code.findbugs + findbugs + 3.0.1 + provided + + + + diff --git a/core/src/main/grammar/OrientSQL.jjt b/core/src/main/grammar/OrientSQL.jjt new file mode 100644 index 00000000000..79040967e4f --- /dev/null +++ b/core/src/main/grammar/OrientSQL.jjt @@ -0,0 +1,4703 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + + +options { + TRACK_TOKENS = true; + JDK_VERSION = "1.6"; + MULTI=true; + VISITOR=true; + STATIC=false; + USER_CHAR_STREAM = true ; + JAVA_UNICODE_ESCAPE=true; + NODE_PREFIX="O"; +} + +PARSER_BEGIN(OrientSql) + +package com.orientechnologies.orient.core.sql.parser; + +import java.io.InputStream; +import java.util.List; +import java.util.ArrayList; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; + +/** Orient Database Sql grammar. */ +public class OrientSql { + + private int inputParamCount = 0; + + + public OrientSql(InputStream stream) { + this(new JavaCharStream(stream)); + } + +} + +PARSER_END(OrientSql) + +SKIP : +{ + " " +| "\t" +| "\n" +| "\r" +| "\f" +} + +/* COMMENTS */ + +MORE : +{ + <"/**" ~["/"]> { input_stream.backup(1); } : IN_FORMAL_COMMENT +| + "/*" : IN_MULTI_LINE_COMMENT +} + + +SPECIAL_TOKEN : +{ + : DEFAULT +} + + +SPECIAL_TOKEN : +{ + : DEFAULT +} + + +MORE : +{ + < ~[] > +} + + +/* reserved words */ +TOKEN: +{ + < SELECT: ( "s" | "S" ) ( "e" | "E" ) ( "l" | "L" ) ( "e" | "E" ) ( "c" | "C" ) ( "t" | "T" ) > + | + < TRAVERSE: ( "t" | "T") ( "r" | "R") ( "a" | "A") ( "v" | "V") ( "e" | "E") ( "r" | "R") ( "s" | "S") ( "e" | "E") > + | + < MATCH: ( "m" | "M" ) ( "a" | "A" ) ( "t" | "T" ) ( "c" | "C" ) ( "h" | "H" ) > + | + < INSERT: ( "i" | "I" ) ( "n" | "N" ) ( "s" | "S" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) > + | + < CREATE: ( "c" | "C" ) ( "r" | "R" ) ( "e" | "E" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < DELETE: ( "d" | "D" ) ( "e" | "E" ) ( "l" | "L" ) ( "e" | "E" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < VERTEX: ( "v" | "V" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) ( "e" | "E" ) ( "x" | "X" ) > + | + < EDGE: ( "e" | "E" ) ( "d" | "D" ) ( "g" | "G" ) ( "e" | "E" ) > + | + < UPDATE: ( "u" | "U" ) ( "p" | "P" ) ( "d" | "D" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < UPSERT: ( "u" | "U" ) ( "p" | "P" ) ( "s" | "S" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) > + | + < FROM: ( "f" | "F" ) ( "r" | "R" ) ( "o" | "O" ) ( "m" | "M" ) > + | + < TO: ( "t" | "T" ) ( "o" | "O" ) > + | + < WHERE: ( "w" | "W" ) ( "h" | "H" ) ( "e" | "E" ) ( "r" | "R" ) ( "e" | "E" ) > + | + < WHILE: ( "w" | "W" ) ( "h" | "H" ) ( "i" | "I" ) ( "l" | "L" ) ( "e" | "E" ) > + | + < INTO: ( "i" | "I" ) ( "n" | "N" ) ( "t" | "T" ) ( "o" | "O" ) > + | + < VALUE: ( "v" | "V" ) ( "a" | "A" ) ( "l" | "L" ) ( "u" | "U" ) ( "e" | "E" ) > + | + < VALUES: ( "v" | "V" ) ( "a" | "A" ) ( "l" | "L" ) ( "u" | "U" ) ( "e" | "E" ) ( "s" | "S" )> + | + < SET: ( "s" | "S" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < ADD: ( "a" | "A" ) ( "d" | "D" ) ( "d" | "D" ) > + | + < PUT: ( "p" | "P" ) ( "u" | "U" ) ( "t" | "T" ) > + | + < MERGE: ( "m" | "M" ) ( "e" | "E" ) ( "r" | "R" ) ( "g" | "G" ) ( "e" | "E" ) > + | + < CONTENT: ( "c" | "C" ) ( "o" | "O" ) ( "n" | "N" ) ( "t" | "T" ) ( "e" | "E" ) ( "n" | "N" ) ( "t" | "T" ) > + | + < REMOVE: ( "r" | "R" ) ( "e" | "E" ) ( "m" | "M" ) ( "o" | "O" ) ( "v" | "V" ) ( "e" | "E" ) > + | + < INCREMENT: ( "i" | "I" ) ( "n" | "N" ) ( "c" | "C" ) ( "r" | "R" ) ( "e" | "E" ) ( "m" | "M" ) ( "e" | "E" ) ( "n" | "N" ) ( "t" | "T" ) > + | + < AND: ( "a" | "A" ) ( "n" | "N" ) ( "d" | "D" ) > + | + < OR: ( "o" | "O" ) ( "r" | "R" ) > + | + < NULL: ( "N" | "n" ) ( "U" | "u" ) ( "L" | "l" ) ( "L" | "l" ) > + | + < DEFINED: ( "D" | "d" ) ( "E" | "e" ) ( "F" | "f" ) ( "I" | "i" ) ( "N" | "n" ) ( "E" | "e" ) ( "D" | "d" ) > + | + < ORDER: ( "o" | "O" ) ( "r" | "R" ) ( "d" | "D" ) ( "e" | "E" ) ( "r" | "R" ) > + | + < GROUP: ( "g" | "G" ) ( "r" | "R" ) ( "o" | "O" ) ( "u" | "U" ) ( "p" | "P" ) > + | + < BY: ( "b" | "B" ) ( "y" | "Y" ) > + | + < LIMIT: ( "l" | "L" ) ( "i" | "I" ) ( "m" | "M" ) ( "i" | "I" ) ( "t" | "T" ) > + | + < SKIP2: ( "s" | "S" ) ( "k" | "K" ) ( "i" | "I" ) ( "p" | "P" ) > + | + < BATCH: ( "b" | "B" ) ( "a" | "A" ) ( "t" | "T" ) ( "c" | "C" ) ( "h" | "H" ) > + | + < OFFSET: ( "o" | "O" ) ( "f" | "F" ) ( "f" | "F" ) ( "s" | "S" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < TIMEOUT: ( "t" | "T" ) ( "i" | "I" ) ( "m" | "M" ) ( "e" | "E" ) ( "o" | "O" ) ( "u" | "U" ) ( "t" | "T" ) > + | + < ASC: ( "a" | "A" ) ( "s" | "S" ) ( "c" | "C" ) > + | + < AS: ( "a" | "A" ) ( "s" | "S" ) > + | + < DESC: ( "d" | "D" ) ( "e" | "E" ) ( "s" | "S" ) ( "c" | "C" ) > + | + < FETCHPLAN: ( "f" | "F" ) ( "e" | "E" ) ( "t" | "T" ) ( "c" | "C" ) ( "h" | "H" ) ( "p" | "P" ) ( "l" | "L" ) ( "a" | "A" ) ( "n" | "N" ) > + | + < RETURN: ( "r" | "R" ) ( "e" | "E" ) ( "t" | "T" ) ( "u" | "U" ) ( "r" | "R" ) ( "n" | "N" ) > + | + < BEFORE: ( "b" | "B" ) ( "e" | "E" ) ( "f" | "F" ) ( "o" | "O" ) ( "r" | "R" ) ( "e" | "E" ) > + | + < AFTER: ( "a" | "A" ) ( "f" | "F" ) ( "t" | "T" ) ( "e" | "E" ) ( "r" | "R" ) > + | + < LOCK: ( "l" | "L" ) ( "o" | "O" ) ( "c" | "C" ) ( "k" | "K" ) > + | + < RECORD: ( "r" | "R" ) ( "e" | "E" ) ( "c" | "C" ) ( "o" | "O" ) ( "r" | "R" ) ( "d" | "D" ) > + | + < WAIT: ( "w" | "W" ) ( "a" | "A" ) ( "i" | "I" ) ( "t" | "T" ) > + | + < RETRY: ( "r" | "R" ) ( "e" | "E" ) ( "t" | "T" ) ( "r" | "R" ) ( "y" | "Y" ) > + | + < LET: ( "l" | "L" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < CACHE: ( "c" | "C" ) ( "a" | "A" ) ( "c" | "C" ) ( "h" | "H" ) ( "e" | "E" ) > + | + < NOCACHE: ( "n" | "N" ) ( "o" | "O" ) ( "c" | "C" ) ( "a" | "A" ) ( "c" | "C" ) ( "h" | "H" ) ( "e" | "E" ) > + | + < UNSAFE: ( "u" | "U" ) ( "n" | "N" ) ( "s" | "S" ) ( "a" | "A" ) ( "f" | "F" ) ( "e" | "E" ) > + | + < PARALLEL: ( "p" | "P" ) ( "a" | "A" ) ( "r" | "R" ) ( "a" | "A" ) ( "l" | "L" ) ( "l" | "L" ) ( "e" | "E" ) ( "l" | "L" ) > + | + < STRATEGY: ( "s" | "S" ) ( "t" | "T" ) ( "r" | "R" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) ( "g" | "G" ) ( "y" | "Y" ) > + | + < DEPTH_FIRST: ( "d" | "D" ) ( "e" | "E" ) ( "p" | "P" ) ( "t" | "T" ) ( "h" | "H" ) ( "_" ) ( "f" | "F" ) ( "i" | "I" ) ( "r" | "R" ) ( "s" | "S" ) ( "t" | "T" ) > + | + < BREADTH_FIRST: ( "b" | "B" ) ( "r" | "R" ) ( "e" | "E" ) ( "a" | "A" ) ( "d" | "D" ) ( "t" | "T" ) ( "h" | "H" ) ( "_" ) ( "f" | "F" ) ( "i" | "I" ) ( "r" | "R" ) ( "s" | "S" ) ( "t" | "T" ) > + | + < LUCENE: ( "l" | "L" ) ( "u" | "U" ) ( "c" | "C" ) ( "e" | "E" ) ( "n" | "N" ) ( "e" | "E" ) > + | + < NEAR: ( "n" | "N" ) ( "e" | "E" ) ( "a" | "A" ) ( "r" | "R" ) > + | + < WITHIN: ( "w" | "W" ) ( "i" | "I" ) ( "t" | "T" ) ( "h" | "H" ) ( "i" | "I" ) ( "n" | "N" ) > + | + < UNWIND: ( "u" | "U" ) ( "n" | "N" ) ( "w" | "W" ) ( "i" | "I" ) ( "n" | "N" ) ( "d" | "D" ) > + | + < MAXDEPTH: ( "m" | "M" ) ( "a" | "A" ) ( "x" | "X" ) ( "d" | "D" ) ( "e" | "E" ) ( "p" | "P" ) ( "t" | "T" ) ( "h" | "H" ) > + | + < MINDEPTH: ( "m" | "M" ) ( "i" | "I" ) ( "n" | "N" ) ( "d" | "D" ) ( "e" | "E" ) ( "p" | "P" ) ( "t" | "T" ) ( "h" | "H" ) > + | + < CLASS: ( "c" | "C" ) ( "l" | "L" ) ( "a" | "A" ) ( "s" | "S" ) ( "s" | "S" ) > + | + < SUPERCLASS: ( "s" | "S" ) ( "u" | "U" ) ( "p" | "P" ) ( "e" | "E" ) ( "r" | "R" ) ( "c" | "C" ) ( "l" | "L" ) ( "a" | "A" ) ( "s" | "S" ) ( "s" | "S" ) > + | + < CLASSES: ( "c" | "C" ) ( "l" | "L" ) ( "a" | "A" ) ( "s" | "S" ) ( "s" | "S" ) ( "e" | "E" ) ( "s" | "S" ) > + | + < SUPERCLASSES: ( "s" | "S" ) ( "u" | "U" ) ( "p" | "P" ) ( "e" | "E" ) ( "r" | "R" ) ( "c" | "C" ) ( "l" | "L" ) ( "a" | "A" ) ( "s" | "S" ) ( "s" | "S" ) ( "e" | "E" ) ( "s" | "S" )> + | + < EXCEPTION: ( "e" | "E" ) ( "x" | "X" ) ( "c" | "C" ) ( "e" | "E" ) ( "p" | "P" ) ( "t" | "T" ) ( "i" | "I" ) ( "o" | "O" ) ( "n" | "N" ) > + | + < PROFILE: ( "p" | "P" ) ( "r" | "R" ) ( "o" | "O" ) ( "f" | "F" ) ( "i" | "I" ) ( "l" | "L" ) ( "e" | "E" ) > + | + < STORAGE: ( "s" | "S" ) ( "t" | "T" ) ( "o" | "O" ) ( "r" | "R" ) ( "a" | "A" ) ( "g" | "G" ) ( "e" | "E" ) > + | + < ON: ( "o" | "O" ) ( "n" | "N" ) > + | + < OFF: ( "o" | "O" ) ( "f" | "F" ) ( "f" | "F" ) > + | + < TRUNCATE: ( "t" | "T" ) ( "r" | "R" ) ( "u" | "U" ) ( "n" | "N" ) ( "c" | "C" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < POLYMORPHIC: ( "p" | "P" ) ( "o" | "O" ) ( "l" | "L" ) ( "y" | "Y" ) ( "m" | "M" ) ( "o" | "O" ) ( "r" | "R" ) ( "p" | "P" ) ( "h" | "H" ) ( "i" | "I" ) ( "c" | "C" ) > + | + < FIND: ( "f" | "F" ) ( "i" | "I" ) ( "n" | "N" ) ( "d" | "D" ) > + | + < REFERENCES: ( "r" | "R" ) ( "e" | "E" ) ( "f" | "F" ) ( "e" | "E" ) ( "r" | "R" ) ( "e" | "E" ) ( "n" | "N" ) ( "c" | "C" ) ( "e" | "E" ) ( "s" | "S" ) > + | + < EXTENDS: ( "e" | "E" ) ( "x" | "X" ) ( "t" | "T" ) ( "e" | "E" ) ( "n" | "N" ) ( "d" | "D" ) ( "s" | "S" ) > + | + < CLUSTERS: ( "C" | "c" ) ( "L" | "l" ) ( "U" | "u" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "R" | "r" ) ( "S" | "s" ) > + | + < ABSTRACT: ( "a" | "A" ) ( "b" | "B" ) ( "s" | "S" ) ( "T" | "t" ) ( "R" | "r" ) ( "a" | "A" ) ( "C" | "c" ) ( "T" | "t" ) > + | + < ALTER: ( "a" | "A" ) ( "l" | "L" ) ( "t" | "T" ) ( "e" | "E" ) ( "r" | "R" ) > + | + < NAME: ("n" | "N") ( "a" | "A" ) ( "m" | "M" ) ( "e" | "E" ) > + | + < SHORTNAME: ( "s" | "S" ) ( "h" | "H" ) ( "o" | "O" ) ( "r" | "R" ) ( "t" | "T" ) ("n" | "N") ( "a" | "A" ) ( "m" | "M" ) ( "e" | "E" ) > + | + < OVERSIZE: ( "o" | "O" ) ( "v" | "V" ) ( "e" | "E" ) ( "r" | "R" ) ( "s" | "S" ) ( "i" | "I" ) ("z" | "Z") ( "e" | "E" ) > + | + < STRICTMODE: ( "s" | "S" ) ( "t" | "T" ) ( "r" | "R" ) ( "i" | "I" ) ( "C" | "c" ) ( "T" | "t" ) ( "m" | "M" ) ( "o" | "O" ) ( "d" | "D" ) ( "e" | "E" ) > + | + < ADDCLUSTER: ( "a" | "A" ) ( "d" | "D" ) ( "d" | "D" ) ( "C" | "c" ) ( "L" | "l" ) ( "U" | "u" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "R" | "r" ) > + | + < REMOVECLUSTER: ( "r" | "R" ) ( "e" | "E" ) ( "m" | "M" ) ( "o" | "O" ) ( "v" | "V" ) ( "e" | "E" ) ( "C" | "c" ) ( "L" | "l" ) ( "U" | "u" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "R" | "r" ) > + | + < CUSTOM: ( "c" | "C" ) ( "u" | "U" ) ( "s" | "S" ) ( "t" | "T" ) ( "o" | "O" ) ( "m" | "M" ) > + | + < CLUSTERSELECTION: ( "C" | "c" ) ( "L" | "l" ) ( "U" | "u" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "R" | "r" ) ( "s" | "S" ) ( "e" | "E" ) ( "l" | "L" ) ( "e" | "E" ) ( "c" | "C" ) ( "t" | "T" ) ( "i" | "I" ) ( "o" | "O" ) ( "n" | "N" ) > + | + < DESCRIPTION: ( "d" | "D" ) ( "E" | "e" ) ( "s" | "S" ) ( "c" | "C" ) ( "r" | "R" ) ( "i" | "I" ) ( "p" | "P" ) ( "t" | "T" ) ( "i" | "I" ) ( "o" | "O" ) ( "n" | "N" ) > + | + < ENCRYPTION: ( "E" | "e" ) ( "n" | "N" ) ( "c" | "C" ) ( "r" | "R" ) ( "y" | "Y" ) ( "p" | "P" ) ( "t" | "T" ) ( "i" | "I" ) ( "o" | "O" ) ( "n" | "N" ) > + | + < DROP: ( "d" | "D" ) ( "r" | "R" ) ( "o" | "O" ) ( "p" | "P" ) > + | + < PROPERTY: ( "p" | "P" ) ( "r" | "R" ) ( "o" | "O" ) ( "p" | "P" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) ( "y" | "Y" ) > + | + < FORCE: ( "f" | "F" ) ( "o" | "O" ) ( "r" | "R" ) ( "c" | "C" ) ( "e" | "E" ) > + | + < METADATA: ( "m" | "M" ) ( "e" | "E" ) ( "t" | "T" ) ( "a" | "A" ) ( "d" | "D" ) ( "a" | "A" ) ( "t" | "T" ) ( "a" | "A" ) > + | + < INDEX: ( "I" | "i") ( "N" | "n") ( "D" | "d") ( "E" | "e") ( "X" | "x") > + | + < COLLATE: ( "c" | "C") ( "o" | "O") ( "l" | "L") ( "l" | "L") ( "a" | "A") ( "t" | "T") ( "E" | "e") > + | + < ENGINE: ( "E" | "e") ( "N" | "n") ( "G" | "g") ( "I" | "i") ( "N" | "n")( "E" | "e") > + | + < REBUILD: ( "R" | "r") ( "E" | "e") ( "B" | "b") ( "U" | "u") ( "I" | "i") ( "L" | "l") ( "D" | "d") > + | + < ID: ( "I" | "i") ( "D" | "d") > + | + < DATABASE: ( "D" | "d") ( "A" | "a") ( "T" | "t") ( "A" | "a") ( "B" | "b") ( "A" | "a") ( "S" | "s") ( "E" | "e") > + | + < OPTIMIZE: ( "O" | "o") ( "P" | "p") ( "T" | "t") ( "I" | "i") ( "M" | "m") ( "I" | "i") ( "Z" | "z") ( "E" | "e") > + | + < LINK: ( "L" | "l") ( "I" | "i") ( "N" | "n") ( "K" | "k") > + | + < TYPE: ( "T" | "t") ( "Y" | "y") ( "P" | "p") ( "E" | "e") > + | + < INVERSE: ( "I" | "i") ( "N" | "n") ( "V" | "v") ( "E" | "e") ( "R" | "r") ( "S" | "s") ( "E" | "e") > + | + < EXPLAIN: ( "E" | "e") ( "X" | "x") ( "P" | "p") ( "L" | "l") ( "A" | "a") ( "I" | "i") ( "N" | "n") > + | + < GRANT: ( "G" | "g") ( "R" | "r") ( "A" | "a") ( "N" | "n") ( "T" | "t") > + | + < REVOKE: ( "R" | "r") ( "E" | "e") ( "V" | "v") ( "O" | "o") ( "K" | "k") ( "E" | "e") > + | + < READ: ( "R" | "r") ( "E" | "e") ( "A" | "a") ( "D" | "d")> + | + < EXECUTE: ( "E" | "e") ( "X" | "x") ( "E" | "e") ( "C" | "c") ( "U" | "u") ( "T" | "t") ( "E" | "e")> + | + < ALL: ( "A" | "a") ( "L" | "l") ( "L" | "l")> + | + < NONE: ( "N" | "n") ( "O" | "o") ( "N" | "n") ( "E" | "e")> + | + < FUNCTION: ( "F" | "f") ( "U" | "u") ( "N" | "n") ( "C" | "c") ( "T" | "t") ( "I" | "i") ( "O" | "o") ( "N" | "n") > + | + < PARAMETERS: ( "P" | "p") ( "A" | "a") ( "R" | "r") ( "A" | "a") ( "M" | "m") ( "E" | "e") ( "T" | "t") ( "E" | "e") ( "R" | "r") ( "S" | "s") > + | + < IDEMPOTENT: ( "I" | "i") ( "D" | "d") ( "E" | "e") ( "M" | "m") ( "P" | "p") ( "O" | "o") ( "T" | "t") ( "E" | "e") ( "N" | "n") ( "T" | "t") > + | + < LANGUAGE: ( "L" | "l") ( "A" | "a") ( "N" | "n") ( "G" | "g") ( "U" | "u") ( "A" | "a") ( "G" | "g") ( "E" | "e") > + | + < BEGIN: ( "B" | "b") ( "E" | "e") ( "G" | "g") ( "I" | "i") ( "N" | "n") > + | + < COMMIT: ( "C" | "c") ( "O" | "o") ( "M" | "m") ( "M" | "m") ( "I" | "i") ( "T" | "t") > + | + < ROLLBACK: ( "R" | "r") ( "O" | "o") ( "L" | "l") ( "L" | "l") ( "B" | "b") ( "A" | "a") ( "C" | "c") ( "K" | "k")> + | + < IF: ( "I" | "i") ( "F" | "f") > + | + < ISOLATION: ( "I" | "i") ( "S" | "s") ( "O" | "o") ( "L" | "l") ( "A" | "a") ( "T" | "t") ( "I" | "i") ( "O" | "o") ( "N" | "n") > + | + < SLEEP: ( "S" | "s") ( "L" | "l") ( "E" | "e") ( "E" | "e") ( "P" | "p") > + | + < CONSOLE: ( "C" | "c") ( "O" | "o") ( "N" | "n") ( "S" | "s") ( "O" | "o") ( "L" | "l") ( "E" | "e")> + | + < BLOB: ( "B" | "b") ( "L" | "l") ( "O" | "o") ( "B" | "b") > + | + < SHARED: ( "S" | "s") ( "H" | "h") ( "A" | "a") ( "R" | "r") ( "E" | "e") ( "D" | "d") > + | + < DEFAULT_: ( "D" | "d") ( "E" | "e") ( "F" | "f") ( "A" | "a") ( "U" | "u") ( "L" | "l") ( "T" | "t") > + | + < SEQUENCE: ( "S" | "s") ( "E" | "e") ( "Q" | "q") ( "U" | "u") ( "E" | "e") ( "N" | "n") ( "C" | "c") ( "E" | "e") > + | + < START: ( "S" | "s") ( "T" | "t") ( "A" | "a") ( "R" | "r") ( "T" | "t") > + | + < OPTIONAL: ( "O" | "o") ( "P" | "p") ( "T" | "t") ( "I" | "i") ( "O" | "o") ( "N" | "n") ( "A" | "a") ( "L" | "l") > + | + < COUNT: ( "C" | "c") ( "O" | "o") ( "U" | "u") ( "N" | "n") ( "T" | "t") > + | + < HA: ( "H" | "h") ( "A" | "a") > + | + < STATUS: ( "S" | "s") ( "T" | "t") ( "A" | "a") ( "T" | "t") ( "U" | "u") ( "S" | "s") > + | + < SERVER: ( "S" | "s") ( "E" | "e") ( "R" | "r") ( "V" | "v") ( "E" | "e") ( "R" | "r") > + | + < SYNC: ( "S" | "s") ( "Y" | "y") ( "N" | "n") ( "C" | "c") > + | + < EXISTS: ( "E" | "e") ( "X" | "x") ( "I" | "i") ( "S" | "s") ( "T" | "t") ( "S" | "s") > + | + < RID: ( "R" | "r") ( "I" | "i") ( "D" | "d") > + | + < RIDS: ( "R" | "r") ( "I" | "i") ( "D" | "d") ( "S" | "s") > + | + < MOVE: ( "m" | "M" ) ( "o" | "O" ) ( "v" | "V" ) ( "e" | "E" ) > + | + < THIS: "@" ( ( "t" | "T" ) ( "h" | "H" ) ( "i" | "I" ) ( "s" | "S" ) ) > + | + < RECORD_ATTRIBUTE: | | | | | | | | > + | + < #RID_ATTR: "@" ( ( "r" | "R" ) ( "i" | "I" ) ( "d" | "D" ) ) > + | + < #CLASS_ATTR: "@" ( ( "c" | "C" ) ( "l" | "L" ) ( "a" | "A" ) ( "s" | "S" ) ( "s" | "S" ))> + | + < #VERSION_ATTR: "@" ( ( "v" | "V" ) ( "e" | "E" ) ( "r" | "R" ) ( "s" | "S" ) ( "i" | "I" ) ( "o" | "O" ) ( "n" | "N" )) > + | + < #SIZE_ATTR: "@" ( ( "s" | "S" ) ( "i" | "I" ) ( "z" | "Z" ) ( "e" | "E" ) ) > + | + < #TYPE_ATTR: "@" ( ( "t" | "T" ) ( "y" | "Y" ) ( "p" | "P" ) ( "e" | "E" ) ) > + | + < #RAW_ATTR: "@" ( ( "r" | "R" ) ( "a" | "A" ) ( "w" | "W" ) ) > + | + < #RID_ID_ATTR: "@" ( ( "r" | "R" ) ( "i" | "I" ) ( "d" | "D" ) "_" ( "i" | "I" ) ( "d" | "D" )) > + | + < #RID_POS_ATTR: "@" ( ( "r" | "R" ) ( "i" | "I" ) ( "d" | "D" ) "_" ( "p" | "P" ) ( "o" | "O" ) ( "s" | "S" )) > + | + < #FIELDS_ATTR: "@" ( ( "f" | "F" ) ( "i" | "I" ) ( "e" | "E" ) ( "l" | "L" ) ( "d" | "D" ) ( "s" | "S" )) > + +} + + +/* LITERALS */ + +TOKEN : +{ + < INTEGER_LITERAL: + (["l","L"])? + | (["l","L"])? + | (["l","L"])? + > +| + < #DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* > +| + < #HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ > +| + < #OCTAL_LITERAL: "0" (["0"-"7"])* > +| + < FLOATING_POINT_LITERAL: + + | + > +| + < #DECIMAL_FLOATING_POINT_LITERAL: + (["0"-"9"])+ "." (["0"-"9"])* ()? (["f","F","d","D"])? + | "." (["0"-"9"])+ ()? (["f","F","d","D"])? + | (["0"-"9"])+ (["f","F","d","D"])? + | (["0"-"9"])+ ()? ["f","F","d","D"] + > +| + < #DECIMAL_EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > +| + < #HEXADECIMAL_FLOATING_POINT_LITERAL: + "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])+ (".")? (["f","F","d","D"])? + | "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])* "." (["0"-"9","a"-"f","A"-"F"])+ (["f","F","d","D"])? + > +| + < #HEXADECIMAL_EXPONENT: ["p","P"] (["+","-"])? (["0"-"9"])+ > +| + < CHARACTER_LITERAL: + "'" + ( (~["'","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\"","/"] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + ) + "'" + > +| + < STRING_LITERAL: + ( + "\"" + ( (~["\"","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\"","/"] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + )* + "\"" + ) + | + ( + "'" + ( (~["\'","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\"","/"] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + )* + "'" + ) + > + | + < INTEGER_RANGE: + ()? ()? + > + | + < TRUE: "true" > + | + < FALSE: "false" > +} + + + +/* SEPARATORS */ + +TOKEN : +{ + < LPAREN: "(" > +| < RPAREN: ")" > +| < LBRACE: "{" > +| < RBRACE: "}" > +| < LBRACKET: "[" > +| < RBRACKET: "]" > +| < SEMICOLON: ";" > +| < COMMA: "," > +| < DOT: "." > +| < AT: "@" > +| < DOLLAR: "$" > +| < BACKTICK: "`" > +} + +/* OPERATORS */ + +TOKEN : +{ + + < EQ: "=" > +| < EQEQ: "==" > +| < LT: "<" > +| < GT: ">" > +| < BANG: "!" > +| < TILDE: "~" > +| < HOOK: "?" > +| < COLON: ":" > +| < LE: "<=" > +| < GE: ">=" > +| < NE: "!=" > +| < NEQ: "<>" > +| < SC_OR: "||" > +| < SC_AND: "&&" > +| < INCR: "++" > +| < DECR: "--" > +| < PLUS: "+" > +| < MINUS: "-" > +| < STAR: "*" > +| < SLASH: "/" > +| < BIT_AND: "&" > +| < BIT_OR: "|" > +| < XOR: "^" > +| < REM: "%" > +| < LSHIFT: "<<" > +| < PLUSASSIGN: "+=" > +| < MINUSASSIGN: "-=" > +| < STARASSIGN: "*=" > +| < SLASHASSIGN: "/=" > +| < ANDASSIGN: "&=" > +| < ORASSIGN: "|=" > +| < XORASSIGN: "^=" > +| < REMASSIGN: "%=" > +| < LSHIFTASSIGN: "<<=" > +| < RSIGNEDSHIFTASSIGN: ">>=" > +| < RUNSIGNEDSHIFTASSIGN: ">>>=" > +| < ELLIPSIS: "..." > +| < RANGE: ".." > +| < NOT: ( "N" | "n") ( "O" | "o") ( "T" | "t") > +| < IN: ( "I" | "i") ( "N" | "n") > +| < LIKE: ( "L" | "l") ( "I" | "i") ( "K" | "k") ( "E" | "e") > +| < IS: "is" | "IS" | "Is" | "iS" > +| < BETWEEN: ( "B" | "b") ( "E" | "e") ( "T" | "t") ( "W" | "w") ( "E" | "e") ( "E" | "e") ( "N" | "n")> +| < CONTAINS: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) > +| < CONTAINSALL: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "A" | "a" ) ( "L" | "l" ) ( "L" | "l" ) > +| < CONTAINSKEY: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "K" | "k" ) ( "E" | "e" ) ( "Y" | "y" ) > +| < CONTAINSVALUE: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "V" | "v" ) ( "A" | "a" ) ( "L" | "l" ) ( "U" | "u" ) ( "E" | "e" ) > +| < CONTAINSTEXT: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "X" | "x" ) ( "T" | "t" ) > +| < MATCHES: ( "M" | "m") ( "A" | "a") ( "T" | "t") ( "C" | "c") ( "H" | "h") ( "E" | "e") ( "S" | "s") > +| < KEY: ( "K" | "k") ( "E" | "e") ( "Y" | "y") > +| < INSTANCEOF: ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "T" | "t" ) ( "A" | "a" ) ( "N" | "n" ) ( "C" | "c" ) ( "E" | "e" ) ( "O" | "o" ) ( "F" | "f" ) > +| < CLUSTER: ( "C" | "c" ) ( "L" | "l" ) ( "U" | "u" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "R" | "r" ) > +} + + + +TOKEN : +{ + < IDENTIFIER: ( (() | ) ()* ) > +| + < QUOTED_IDENTIFIER: ( "`" (~["`"] | "\\`") (~["`"] | "\\`")* "`") > +| + < INDEX_COLON: ":" > +| + < INDEXVALUES_IDENTIFIER: ( "I" | "i") ( "N" | "n") ( "D" | "d") ( "E" | "e") ( "X" | "x") ( "V" | "v") ( "A" | "a") ( "L" | "l") ( "U" | "u") ( "E" | "e") ( "S" | "s") ":" ( "__@recordmap@___" )? ( ( | ) )* > +| + < INDEXVALUESASC_IDENTIFIER:( "I" | "i") ( "N" | "n") ( "D" | "d") ( "E" | "e") ( "X" | "x") ( "V" | "v") ( "A" | "a") ( "L" | "l") ( "U" | "u") ( "E" | "e") ( "S" | "s") ( "A" | "a") ( "S" | "s") ( "C" | "c") ":" ( "__@recordmap@___" )? ( ( | ) )* > +| + < INDEXVALUESDESC_IDENTIFIER: ( "I" | "i") ( "N" | "n") ( "D" | "d") ( "E" | "e") ( "X" | "x") ( "V" | "v") ( "A" | "a") ( "L" | "l") ( "U" | "u") ( "E" | "e") ( "S" | "s") ( "D" | "d") ( "E" | "e") ( "S" | "s") ( "C" | "c") ":" ( "__@recordmap@___" )? ( ( | ) )* > +| + < CLUSTER_IDENTIFIER: > +| + < CLUSTER_NUMBER_IDENTIFIER: > +| + < METADATA_IDENTIFIER: "metadata:" > +| + < #LETTER: + [ "A"-"Z", + "_", + "a"-"z" + ] + > +| + < #PART_LETTER: + [ "0"-"9", + "A"-"Z", + "_", + "a"-"z" + ] + > +} + +ORid Rid(): +{} +{ + ( + LOOKAHEAD(4) + "#" jjtThis.cluster = Integer() jjtThis.position = Integer() + | + LOOKAHEAD(3) + jjtThis.cluster = Integer() jjtThis.position = Integer() + ) + { return jjtThis; } +} + +/** Root productions. */ +OStatement parse() : +{OStatement result;} +{ + result = Statement() + { return result; } +} + +List parseScript() : +{ + List result = new ArrayList(); + OStatement last; +} +{ + ( + LOOKAHEAD(StatementSemicolon()) + last = StatementSemicolon() {result.add(last);} + | + last = IfStatement() {result.add(last);} + | + + )* + + + { return result; } +} + +OIdentifier Identifier(): +{ + Token quotedToken = null; + Token token = null; + +} +{ +( + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + quotedToken = +) { + + if(token!=null){ + jjtThis.value = token.image; + }else{ + jjtThis.quoted = true; + jjtThis.value = quotedToken.image; + jjtThis.value = jjtThis.value.substring(1, jjtThis.value.length() - 1); + /*try{ + jjtThis.value = java.net.URLEncoder.encode(jjtThis.value, null); + }catch(Exception e){ + + }*/ + } + + return jjtThis; + + + } +} + +OInteger Integer(): +{ + int sign = 1; + Token tokenVal; +} +{ +( + [ {sign = -1;} ] tokenVal = {jjtThis.value = sign * Long.parseLong(tokenVal.image);} +) { return jjtThis; } +} + + + +OFloatingPoint FloatingPoint(): +{ + String stringValue; + Token tokenVal; +} +{ + ( + [ { jjtThis.sign = -1; } ] tokenVal = { jjtThis.stringValue = tokenVal.image; } + ) + { return jjtThis; } +} + +ONumber Number(): +{ ONumber result; } +{ + ( + LOOKAHEAD( Integer() ) + result = Integer() + | + LOOKAHEAD( FloatingPoint() ) + result = FloatingPoint() + ) + { return result; } +} + +OStatement Statement(): +{OStatement result = null;} +{ + result = StatementInternal() + [ ] + {return result;} +} + +OStatement StatementSemicolon(): +{OStatement result = null;} +{ + result = StatementInternal() + + {return result;} +} + +OStatement StatementInternal(): +{ + OStatement result = null; +} +{ + ( + ( + ( + result = QueryStatement() + | + LOOKAHEAD(2) + result = DeleteStatement() + | + LOOKAHEAD(2) + result = DeleteVertexStatement() + | + LOOKAHEAD(2) + result = DeleteEdgeStatement() + | + result = InsertStatement() + | + LOOKAHEAD(2) + result = CreateClassStatement() + | + LOOKAHEAD(2) + result = CreatePropertyStatement() + | + LOOKAHEAD(2) + result = CreateIndexStatement() + | + LOOKAHEAD(2) + result = CreateClusterStatement() + | + LOOKAHEAD(2) + result = CreateLinkStatement() + | + LOOKAHEAD(2) + result = CreateFunctionStatement() + | + LOOKAHEAD(2) + result = CreateSequenceStatement() + | + LOOKAHEAD(CreateVertexStatementNoTarget()) + result = CreateVertexStatementNoTarget() + | + LOOKAHEAD(CreateVertexStatement()) + result = CreateVertexStatement() + | + LOOKAHEAD(CreateVertexStatementEmpty()) + result = CreateVertexStatementEmpty() + | + LOOKAHEAD(CreateVertexStatementEmptyNoTarget()) + result = CreateVertexStatementEmptyNoTarget() + | + LOOKAHEAD(MoveVertexStatement()) + result = MoveVertexStatement() + | + LOOKAHEAD(CreateEdgeStatement()) + result = CreateEdgeStatement() + | + LOOKAHEAD(UpdateEdgeStatement()) + result = UpdateEdgeStatement() + | + LOOKAHEAD(UpdateStatement()) + result = UpdateStatement() + | + result = ProfileStorageStatement() + | + LOOKAHEAD(TruncateClassStatement()) + result = TruncateClassStatement() + | + LOOKAHEAD(TruncateClusterStatement()) + result = TruncateClusterStatement() + | + LOOKAHEAD(TruncateRecordStatement()) + result = TruncateRecordStatement() + | + LOOKAHEAD(2) + result = AlterSequenceStatement() + | + LOOKAHEAD(AlterClassStatement()) + result = AlterClassStatement() + | + LOOKAHEAD(2) + result = DropSequenceStatement() + | + LOOKAHEAD(DropClassStatement()) + result = DropClassStatement() + | + LOOKAHEAD(AlterPropertyStatement()) + result = AlterPropertyStatement() + | + LOOKAHEAD(DropPropertyStatement()) + result = DropPropertyStatement() + | + result = RebuildIndexStatement() + | + LOOKAHEAD(2) + result = DropIndexStatement() + | + LOOKAHEAD(AlterClusterStatement()) + result = AlterClusterStatement() + | + LOOKAHEAD(2) + result = DropClusterStatement() + | + LOOKAHEAD(2) + result = AlterDatabaseStatement() + | + result = OptimizeDatabaseStatement() + | + result = GrantStatement() + | + result = RevokeStatement() + | + result = BeginStatement() + | + result = CommitStatement() + | + result = RollbackStatement() + | + result = ReturnStatement() + | + result = SleepStatement() + | + result = ConsoleStatement() + | + result = IfStatement() + | + LOOKAHEAD(2) + result = HaStatusStatement() + | + LOOKAHEAD(2) + result = HaRemoveServerStatement() + | + LOOKAHEAD(3) + result = HaSyncDatabaseStatement() + | + LOOKAHEAD(3) + result = HaSyncClusterStatement() + ) + ) + | + result = ExplainStatement() + | + result = LetStatement() + + ) + { + return result; + } +} + +OStatement QueryStatement(): +{ + OStatement result; +} +{ + ( + LOOKAHEAD( SelectStatement() ) + result = SelectStatement() + | + result = SelectWithoutTargetStatement() + | + result = TraverseStatement() + | + result = MatchStatement() + | + LOOKAHEAD( FindReferencesStatement() ) + result = FindReferencesStatement() + ){ return result; } +} + +OSelectWithoutTargetStatement SelectWithoutTargetStatement(): +{} +{ + ( + + [ jjtThis.projection = Projection() ] + + jjtThis.target = FromClause() + [ jjtThis.letClause = LetClause() ] + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.groupBy = GroupBy() ] + [ jjtThis.orderBy = OrderBy() ] + [ jjtThis.unwind = Unwind() ] + ( + [ + jjtThis.skip = Skip() [ jjtThis.limit = Limit() ] + | + jjtThis.limit = Limit() [ jjtThis.skip = Skip() ] + ] + ) + [ jjtThis.fetchPlan = FetchPlan() ] + [ jjtThis.timeout = Timeout() ] + [ + ( + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT;} + ) + ] + [ { jjtThis.parallel = true; } ] + [ { jjtThis.noCache = true; } ] + ) + { + jjtThis.validate(); + return jjtThis; + } +} + +OTraverseStatement TraverseStatement(): +{ OTraverseProjectionItem lastProjection;} +{ + ( + + [ + lastProjection = TraverseProjectionItem() { jjtThis.projections.add(lastProjection); } + ( lastProjection = TraverseProjectionItem() { jjtThis.projections.add(lastProjection); } )* + ] + + jjtThis.target = FromClause() + [ jjtThis.maxDepth = Integer() ] + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ + ( + { jjtThis.strategy = OTraverseStatement.Strategy.DEPTH_FIRST; } + | + { jjtThis.strategy = OTraverseStatement.Strategy.BREADTH_FIRST; } + ) + ] + ) + {return jjtThis;} +} + +OMatchStatement MatchStatement(): +{ + OMatchExpression lastMatchExpr = null; + OExpression lastReturn = null; + OIdentifier lastReturnAlias = null; +} +{ + ( + + lastMatchExpr = MatchExpression() { jjtThis.matchExpressions.add(lastMatchExpr); } + ( + + lastMatchExpr = MatchExpression() { jjtThis.matchExpressions.add(lastMatchExpr); } + )* + + lastReturn = Expression() {lastReturnAlias = null;} + [ lastReturnAlias = Identifier() ] + { + jjtThis.returnAliases.add(lastReturnAlias); + jjtThis.returnItems.add(lastReturn); + } + ( + + lastReturn = Expression() {lastReturnAlias = null;} + [ lastReturnAlias = Identifier() ] + { + jjtThis.returnAliases.add(lastReturnAlias); + jjtThis.returnItems.add(lastReturn); + } + )* + [ jjtThis.limit = Limit() ] + ){ return jjtThis; } +} + +ODeleteStatement DeleteStatement(): +{} +{ +( + + + jjtThis.fromClause = FromClause() + [ { jjtThis.returnBefore = true; } ] + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ { jjtThis.unsafe = true; }] +) {return jjtThis;} +} + +ODeleteVertexStatement DeleteVertexStatement(): +{} +{ +( + + + [ {jjtThis.from = true;} ] + jjtThis.fromClause = FromClause() + [ { jjtThis.returnBefore = true; } ] + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ jjtThis.batch = Batch() ] +) {return jjtThis;} +} + +OMoveVertexStatement MoveVertexStatement(): +{ OExpression lastSetExpr; } +{ + ( + + jjtThis.source = FromItem() + + ( + jjtThis.targetCluster = Cluster() + | + ( + + + jjtThis.targetClass = Identifier() + ) + ) + [ jjtThis.updateOperations = UpdateOperations() ] + [ jjtThis.batch = Batch() ] + ){ return jjtThis; } +} + +ODeleteEdgeStatement DeleteEdgeStatement(): +{ ODeleteEdgeStatement result; } +{ + ( + LOOKAHEAD(DeleteEdgeByRidStatement()) + result = DeleteEdgeByRidStatement() + | + LOOKAHEAD(DeleteEdgeFromToStatement()) + result = DeleteEdgeFromToStatement() + | + LOOKAHEAD(DeleteEdgeVToStatement()) + result = DeleteEdgeVToStatement() + | + LOOKAHEAD(DeleteEdgeToStatement()) + result = DeleteEdgeToStatement() + | + LOOKAHEAD(DeleteEdgeWhereStatement()) + result = DeleteEdgeWhereStatement() + ) + {return result;} +} + + +ODeleteEdgeStatement DeleteEdgeByRidStatement(): +{ + ORid lastRid; +} +{ +( + + + ( + jjtThis.rid = Rid() + | + ( + + + [ + lastRid = Rid() + { + jjtThis.rids = new ArrayList(); + jjtThis.rids.add(lastRid); + } + ( + + lastRid = Rid() { jjtThis.rids.add(lastRid); } + )* + ] + ) + ) + [ jjtThis.batch = Batch() ] + + +) {return jjtThis;} +} + + + +ODeleteEdgeStatement DeleteEdgeFromToStatement(): +{ + ORid lastRid; +} +{ +( + + + + [ jjtThis.className = Identifier() ] + + + + ( + jjtThis.leftRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtThis.leftRids=new ArrayList(); + jjtThis.leftRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtThis.leftRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtThis.leftStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtThis.leftStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtThis.leftParam = InputParameter() + | + jjtThis.leftIdentifier = Identifier() + ) + + [ + + ( + jjtThis.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtThis.rightRids=new ArrayList(); + jjtThis.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtThis.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtThis.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtThis.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtThis.rightParam = InputParameter() + | + jjtThis.rightIdentifier = Identifier() + ) + ] + + + + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ jjtThis.batch = Batch() ] + +) {return jjtThis;} +} + + +ODeleteEdgeStatement DeleteEdgeToStatement(): +{ + ORid lastRid; +} +{ + ( + + + + jjtThis.className = Identifier() + + + ( + jjtThis.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtThis.rightRids=new ArrayList(); + jjtThis.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtThis.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtThis.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtThis.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtThis.rightParam = InputParameter() + | + jjtThis.rightIdentifier = Identifier() + ) + + + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ jjtThis.batch = Batch() ] + + ) + {return jjtThis;} +} + +ODeleteEdgeStatement DeleteEdgeVToStatement(): +{ + ORid lastRid; +} +{ + ( + + + + + ( + jjtThis.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtThis.rightRids=new ArrayList(); + jjtThis.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtThis.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtThis.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtThis.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtThis.rightParam = InputParameter() + | + jjtThis.rightIdentifier = Identifier() + ) + + + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ jjtThis.batch = Batch() ] + + ) + {return jjtThis;} +} + +ODeleteEdgeStatement DeleteEdgeWhereStatement(): +{ + ORid lastRid; +} +{ + ( + + + + [ jjtThis.className = Identifier() ] + + [ jjtThis.whereClause = WhereClause() ] + [ jjtThis.limit = Limit() ] + [ jjtThis.batch = Batch() ] + ) + {return jjtThis;} +} + +OUpdateEdgeStatement UpdateEdgeStatement(): +{ OUpdateOperations lastOperations; + ORid lastRid;} +{ + ( + + + jjtThis.target = FromClause() + ( lastOperations = UpdateOperations() { jjtThis.operations.add(lastOperations); } )+ + [ { jjtThis.upsert = true; } ] + [ + + ( { jjtThis.returnBefore = true; } | { jjtThis.returnAfter = true; } ) + [ + jjtThis.returnProjection = Projection() + ] + ] + [ jjtThis.whereClause = WhereClause() ] + [ + ( + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT;} + ) + ] + [ jjtThis.limit = Limit() ] + [ jjtThis.timeout = Timeout() ] + ) + {return jjtThis;} +} + +OUpdateStatement UpdateStatement(): +{ OUpdateOperations lastOperations; + ORid lastRid;} +{ + ( + + jjtThis.target = FromClause() + ( lastOperations = UpdateOperations() { jjtThis.operations.add(lastOperations); } )+ + [ { jjtThis.upsert = true; } ] + [ + + ( { jjtThis.returnBefore = true; } | { jjtThis.returnAfter = true; } | { jjtThis.returnCount = true; }) + [ + jjtThis.returnProjection = Projection() + ] + ] + [ jjtThis.let = LetClause() ] + [ jjtThis.whereClause = WhereClause() ] + [ + ( + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK;} + | + {jjtThis.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT;} + ) + ] + [ jjtThis.limit = Limit() ] + [ jjtThis.timeout = Timeout() ] + ) + {return jjtThis;} +} + +OUpdateOperations UpdateOperations(): +{ + OUpdateItem lastItem; + OUpdatePutItem lastPutItem; + OUpdateIncrementItem lastIncrementItem; + OUpdateRemoveItem lastRemoveItem; +} +{ + ( + ( + { jjtThis.type = OUpdateOperations.TYPE_SET; } + lastItem = UpdateItem() { jjtThis.updateItems.add(lastItem); } + ( + lastItem = UpdateItem() { jjtThis.updateItems.add(lastItem); } + )* + ) + | + ( + { jjtThis.type = OUpdateOperations.TYPE_PUT; } + lastPutItem = UpdatePutItem() { jjtThis.updatePutItems.add(lastPutItem); } + ( + lastPutItem = UpdatePutItem() { jjtThis.updatePutItems.add(lastPutItem); } + )* + ) + | + ( + ( + { jjtThis.type = OUpdateOperations.TYPE_MERGE; } + | + { jjtThis.type = OUpdateOperations.TYPE_CONTENT; } + ) + jjtThis.json = Json() + ) + | + ( + ( + { jjtThis.type = OUpdateOperations.TYPE_INCREMENT; } + | + { jjtThis.type = OUpdateOperations.TYPE_ADD; } + ) + lastIncrementItem = UpdateIncrementItem() { jjtThis.updateIncrementItems.add(lastIncrementItem); } + ( + lastIncrementItem = UpdateIncrementItem() { jjtThis.updateIncrementItems.add(lastIncrementItem); } + )* + ) + | + ( + { jjtThis.type = OUpdateOperations.TYPE_REMOVE; } + lastRemoveItem = UpdateRemoveItem() { jjtThis.updateRemoveItems.add(lastRemoveItem); } + ( + + lastRemoveItem = UpdateRemoveItem() { jjtThis.updateRemoveItems.add(lastRemoveItem); } + )* + ) + ) + { return jjtThis; } +} + + +OUpdateItem UpdateItem(): +{} +{ +( + jjtThis.left = Identifier() + [ jjtThis.leftModifier = Modifier() ] + ( + { jjtThis.operator = OUpdateItem.OPERATOR_EQ; } + | + { jjtThis.operator = OUpdateItem.OPERATOR_PLUSASSIGN; } + | + { jjtThis.operator = OUpdateItem.OPERATOR_MINUSASSIGN; } + | + { jjtThis.operator = OUpdateItem.OPERATOR_STARASSIGN; } + | + { jjtThis.operator = OUpdateItem.OPERATOR_SLASHASSIGN; } + ) + jjtThis.right = Expression() +) { return jjtThis; } +} + +OUpdateIncrementItem UpdateIncrementItem(): +{} +{ + ( + jjtThis.left = Identifier() + [ jjtThis.leftModifier = Modifier() ] + + jjtThis.right = Expression() + ) + { return jjtThis; } +} + +OUpdateRemoveItem UpdateRemoveItem(): +{} +{ + ( + jjtThis.left = Identifier() + [ jjtThis.leftModifier = Modifier() ] + [ jjtThis.right = Expression() ] + ) + { return jjtThis; } +} + +OUpdatePutItem UpdatePutItem(): +{} +{ + ( + jjtThis.left = Identifier() jjtThis.key = Expression() jjtThis.value = Expression() + ) + { return jjtThis; } +} + + +OUpdateAddItem UpdateAddItem(): +{} +{ + ( + jjtThis.left = Identifier() + jjtThis.right = Expression() + ) + { return jjtThis; } +} + + +OInsertStatement InsertStatement(): +{} +{ +( + + + ( + LOOKAHEAD(IndexIdentifier()) + jjtThis.targetIndex = IndexIdentifier() + | + jjtThis.targetClass = Identifier() [ jjtThis.targetClusterName = Identifier()] + | + jjtThis.targetCluster = Cluster() + + ) + [ LOOKAHEAD(InsertBody()) jjtThis.insertBody = InsertBody() ] + [ jjtThis.returnStatement = Projection() ] + [ + [ { jjtThis.selectWithFrom = true; } ] + ( + ( + LOOKAHEAD( SelectStatement() ) + jjtThis.selectStatement = SelectStatement() + | + jjtThis.selectStatement = SelectWithoutTargetStatement() + ) + | + LOOKAHEAD(2) + ( + + ( + LOOKAHEAD( SelectStatement() ) + jjtThis.selectStatement = SelectStatement() + | + jjtThis.selectStatement = SelectWithoutTargetStatement() + ) + { jjtThis.selectInParentheses = true; } + + + ) + ) + ] + [ { jjtThis.unsafe = true; }] +) {return jjtThis;} +} + + +OInsertBody InsertBody(): +{ + OIdentifier lastIdentifier; + OExpression lastExpression; + List lastExpressionList; +} +{ + ( + ( + LOOKAHEAD(3) + ( + + lastIdentifier = Identifier() + { + jjtThis.identifierList = new ArrayList(); + jjtThis.identifierList.add(lastIdentifier); + } + ( + + lastIdentifier = Identifier() { jjtThis.identifierList.add(lastIdentifier); } + )* + + + + { + jjtThis.valueExpressions = new ArrayList>(); + lastExpressionList = new ArrayList(); + jjtThis.valueExpressions.add(lastExpressionList); + } + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + ( + + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + )* + + ( + + + { + lastExpressionList = new ArrayList(); + jjtThis.valueExpressions.add(lastExpressionList); + } + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + ( + + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + )* + + )* + ) + | + LOOKAHEAD(3) + ( + + { + jjtThis.setExpressions = new ArrayList(); + OInsertSetExpression lastSetExpr = new OInsertSetExpression(); + jjtThis.setExpressions.add(lastSetExpr); + } + lastSetExpr.left = Identifier() lastSetExpr.right = Expression() + + ( + + { + lastSetExpr = new OInsertSetExpression(); + jjtThis.setExpressions.add(lastSetExpr); + } + lastSetExpr.left = Identifier() lastSetExpr.right = Expression() + )* + ) + | + ( jjtThis.content = Json() ) + ) + //[ jjtThis.returnProjection = Projection() ] + ) + { return jjtThis; } +} + +OCreateVertexStatementEmptyNoTarget CreateVertexStatementEmptyNoTarget(): +{} +{ + + + {return jjtThis;} +} + +OCreateVertexStatementEmpty CreateVertexStatementEmpty(): +{} +{ + + + + jjtThis.targetClass = Identifier() + [ + + jjtThis.targetClusterName = Identifier() + ] + {return jjtThis;} +} + + +OCreateVertexStatement CreateVertexStatement(): +{} +{ +( + + + ( + LOOKAHEAD( Identifier() ) + ( + jjtThis.targetClass = Identifier() + [ + + jjtThis.targetClusterName = Identifier() + ] + ) + | + LOOKAHEAD( Cluster() ) + jjtThis.targetCluster = Cluster() + ) + [ jjtThis.returnStatement = Projection() ] + [ LOOKAHEAD(InsertBody()) jjtThis.insertBody = InsertBody() ] +) {return jjtThis;} +} + + +OCreateVertexStatementNoTarget CreateVertexStatementNoTarget(): +{} +{ +( + + + jjtThis.insertBody = InsertBody() +) {return jjtThis;} +} + + +OCreateEdgeStatement CreateEdgeStatement(): +{ + ORid lastRid; +} +{ +( + + + [ jjtThis.targetClass = Identifier() [ jjtThis.targetClusterName = Identifier()]] + + ( + jjtThis.leftExpression = Expression() + ) + + ( + jjtThis.rightExpression = Expression() + ) + [ jjtThis.body = InsertBody() ] + [ jjtThis.retry = Retry() ] + [ jjtThis.wait = Wait() ] + [ jjtThis.batch = Batch() ] +) {return jjtThis;} +} + + +OInputParameter InputParameter(): +{ OInputParameter result; } +{ + ( + result = PositionalParameter() + | + result = NamedParameter() + ) + { return result; } +} + +OPositionalParameter PositionalParameter(): +{} +{ + + { + jjtThis.paramNumber = inputParamCount; + inputParamCount++; + return jjtThis; + } +} + +ONamedParameter NamedParameter(): +{ +OIdentifier identifierParam; +Token token; +} +{ + ( + + ( + identifierParam = Identifier() { jjtThis.paramName = identifierParam.toString(); } + | + token = {jjtThis.paramName = token.image;} + | + token = {jjtThis.paramName = token.image;} + | + token = {jjtThis.paramName = token.image;} + ) + ) + { + jjtThis.paramNumber = inputParamCount; + inputParamCount++; + return jjtThis; + } +} + +OProjection Projection(): +{ + java.util.List items = new java.util.ArrayList(); + OProjectionItem lastItem = null; +} +{ + ( + lastItem = ProjectionItem() {items.add(lastItem);} ( "," lastItem = ProjectionItem() {items.add(lastItem);} )* + ) + { + jjtThis.items = items; + return jjtThis; + } +} + +OProjectionItem ProjectionItem(): +{} +{ +( + jjtThis.expression = Expression() + [ jjtThis.alias = Alias() ] +){return jjtThis;} +} + + +OArraySelector ArraySelector(): +{} +{ + ( + LOOKAHEAD( Rid() ) + jjtThis.rid = Rid() + | + LOOKAHEAD( InputParameter() ) + jjtThis.inputParam = InputParameter() + | + LOOKAHEAD( Expression() ) + jjtThis.expression = Expression() + ) + { return jjtThis; } +} + +OArrayNumberSelector ArrayNumberSelector(): +{ Token tokenVal; } +{ + ( + LOOKAHEAD( InputParameter() ) + jjtThis.inputValue = InputParameter() + | + LOOKAHEAD( Integer() ) + tokenVal = { jjtThis.integer = Integer.parseInt(tokenVal.image); } + /* TODO for 3.0 + | + LOOKAHEAD( MathExpression() ) + jjtThis.expressionValue = MathExpression() + */ + + ) + { return jjtThis; } +} + +OArraySingleValuesSelector ArraySingleValuesSelector(): +{ OArraySelector lastSelector; } +{ + ( + lastSelector = ArraySelector() { jjtThis.items.add(lastSelector); } + ( lastSelector = ArraySelector() { jjtThis.items.add(lastSelector); } ) * + ) + { return jjtThis; } +} + +OArrayRangeSelector ArrayRangeSelector(): +{ Token token; } +{ + ( + + /* TODO for 3.0 + token = + { + String img = token.image; + String[] splitted = img.split(".."); + jjtThis.from = Integer.parseInt(splitted[0], 10); + jjtThis.to = Integer.parseInt(splitted[1], 10); + } + | + */ + ( + jjtThis.fromSelector = ArrayNumberSelector() [ | { jjtThis.newRange = true; } ] jjtThis.toSelector = ArrayNumberSelector() + ) + ) + { return jjtThis; } +} + + +OIdentifier Alias(): +{ OIdentifier identifier; } +{ + identifier = Identifier() + {return identifier;} +} + +ORecordAttribute RecordAttribute(): +{ Token token; } +{ + ( + token = { jjtThis.name = token.image; } + ) + { return jjtThis; } +} + +OFunctionCall FunctionCall(): +{ + OExpression lastExpression = null; +} +{ + ( + ( + jjtThis.name = Identifier() + ) + + ( + [ + lastExpression = Expression() {jjtThis.params.add(lastExpression);} ( lastExpression = Expression() {jjtThis.params.add(lastExpression);})* + ] + + ) + + ) + { return jjtThis; } +} + +OMethodCall MethodCall(): +{ OExpression lastExpression; } +{ + ( + jjtThis.methodName = Identifier() + [ + lastExpression = Expression() { jjtThis.params.add(lastExpression); } + ( lastExpression = Expression() { jjtThis.params.add(lastExpression); } )* + ] + ) + { return jjtThis; } +} + +OLevelZeroIdentifier LevelZeroIdentifier(): +{} +{ + ( + LOOKAHEAD( FunctionCall() ) + jjtThis.functionCall = FunctionCall() + | + { jjtThis.self = true; } + | + LOOKAHEAD( Collection() ) + jjtThis.collection = Collection() + ) + { return jjtThis; } +} + +OSuffixIdentifier SuffixIdentifier(): +{} +{ + ( + LOOKAHEAD( Identifier() ) + jjtThis.identifier = Identifier() + | + LOOKAHEAD( RecordAttribute() ) + jjtThis.recordAttribute = RecordAttribute() + | + ( { jjtThis.star = true; } ) + ) + { return jjtThis; } +} + + +OBaseIdentifier BaseIdentifier(): +{} +{ + ( + LOOKAHEAD( LevelZeroIdentifier() ) + jjtThis.levelZero = LevelZeroIdentifier() + | + LOOKAHEAD( SuffixIdentifier() ) + jjtThis.suffix = SuffixIdentifier() + ) + { return jjtThis; } +} + +OModifier Modifier(): +{} +{ + ( + ( + ( + { jjtThis.squareBrackets = true; } + ( + LOOKAHEAD( ArrayRangeSelector() ) + jjtThis.arrayRange = ArrayRangeSelector() + | + LOOKAHEAD( OrBlock() ) + jjtThis.condition = OrBlock() + | + LOOKAHEAD( ArraySingleValuesSelector() ) + jjtThis.arraySingleValues = ArraySingleValuesSelector() + ) + + ) + | + LOOKAHEAD( MethodCall() ) + jjtThis.methodCall = MethodCall() + | + jjtThis.suffix = SuffixIdentifier() + ) + [ + LOOKAHEAD( Modifier() ) + jjtThis.next = Modifier() + ] + ) + { return jjtThis; } +} + +OExpression Expression(): +{Token token; } +{ + ( + {jjtThis.value = null;} + | + LOOKAHEAD( Rid() ) + jjtThis.value = Rid() + | + LOOKAHEAD( MathExpression() ) + jjtThis.value = MathExpression() + | + jjtThis.value = Json() + | + {jjtThis.value = true;} + | + {jjtThis.value = false;} + ) + { return jjtThis; } +} + +OMathExpression MathExpression(): +{ + OMathExpression sub; + jjtThis.setChildExpressions(new java.util.ArrayList()); +} +{ + ( + sub = MultExpression() { jjtThis.getChildExpressions().add(sub); } + ( + LOOKAHEAD( 2 ) ( { jjtThis.operators.add( OMathExpression.Operator.PLUS); } | { jjtThis.operators.add(OMathExpression.Operator.MINUS); }) + sub = MultExpression() { jjtThis.getChildExpressions().add(sub); } + )* + ) + { + if(jjtThis.getChildExpressions().size() != 1){ + return jjtThis; + }else{ + return jjtThis.getChildExpressions().get(0); + } + } +} + + +OMathExpression MultExpression(): +{ + OMathExpression sub; + jjtThis.setChildExpressions(new java.util.ArrayList()); +} +{ + ( + sub = FirstLevelExpression() { jjtThis.getChildExpressions().add(sub); } + ( + LOOKAHEAD( 2 ) + ( + { jjtThis.operators.add( OMathExpression.Operator.STAR); } + | + { jjtThis.operators.add( OMathExpression.Operator.SLASH); } + | + { jjtThis.operators.add( OMathExpression.Operator.REM); } + ) + sub = FirstLevelExpression() { jjtThis.getChildExpressions().add(sub); } + )* + ) + { + if(jjtThis.getChildExpressions().size() != 1){ + return jjtThis; + }else{ + return jjtThis.getChildExpressions().get(0); + } + } +} + +OMathExpression FirstLevelExpression(): +{ OMathExpression expr;} +{ + ( + LOOKAHEAD( ParenthesisExpression() ) + expr = ParenthesisExpression() + | + LOOKAHEAD( BaseExpression() ) + expr = BaseExpression() + ) + {return expr;} +} + +OMathExpression ParenthesisExpression(): +{} +{ + ( + + ( + LOOKAHEAD(2) + jjtThis.statement = QueryStatement() + | + jjtThis.expression = Expression() + | + jjtThis.statement = InsertStatement() + ) + + ) + {return jjtThis;} +} + +OBaseExpression BaseExpression(): +{} +{ + ( + jjtThis.number = Number() + | + ( + jjtThis.identifier = BaseIdentifier() + [ + LOOKAHEAD( Modifier() ) + jjtThis.modifier = Modifier() + ] + ) + | + ( + jjtThis.inputParam = InputParameter() + [ + LOOKAHEAD( Modifier() ) + jjtThis.modifier = Modifier() + ] + ) + | + ( + ( + token = { jjtThis.string = token.image; } + | + token = { jjtThis.string = token.image; } + ) + [ + LOOKAHEAD( Modifier() ) + jjtThis.modifier = Modifier() + ] + ) + + ) + {return jjtThis;} +} + + + +OFromClause FromClause(): +{} +{ + jjtThis.item = FromItem() + { return jjtThis; } +} + +OLetClause LetClause(): +{ + OLetItem lastItem; +} +{ + ( + lastItem = LetItem() { jjtThis.items.add(lastItem); } ( lastItem = LetItem() { jjtThis.items.add(lastItem); } )* + ) + { return jjtThis; } +} + +OLetItem LetItem(): +{ } +{ + ( + jjtThis.varName = Identifier() + ( + LOOKAHEAD( Expression() ) + jjtThis.expression = Expression() + | + ( + + ( + jjtThis.query = QueryStatement() + ) + + ) + ){ + if(jjtThis.varName.getStringValue().equalsIgnoreCase("$root")|| + jjtThis.varName.getStringValue().equalsIgnoreCase("root")|| + jjtThis.varName.getStringValue().equalsIgnoreCase("$parent")|| + jjtThis.varName.getStringValue().equalsIgnoreCase("parent")|| + jjtThis.varName.getStringValue().equalsIgnoreCase("$current")|| + jjtThis.varName.getStringValue().equalsIgnoreCase("current")){ + throw new OCommandSQLParsingException("invalid LET statement: "+jjtThis.varName+" is a reserved keyword"); + } + + } + ) + { + return jjtThis; + } +} + + +OFromItem FromItem(): +{ + jjtThis.rids = new java.util.ArrayList(); + ORid lastRid; +} +{ + ( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + | + /*( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + ( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + )* + ) + |*/ + jjtThis.cluster = Cluster() + | + jjtThis.clusterList = ClusterList() + | + LOOKAHEAD(IndexIdentifier()) + jjtThis.index = IndexIdentifier() + | + jjtThis.metadata = MetadataIdentifier() + | + jjtThis.statement = QueryStatement() + | + jjtThis.inputParam = InputParameter() + | + ( + jjtThis.identifier = BaseIdentifier() + [ + LOOKAHEAD( Modifier() ) + jjtThis.modifier = Modifier() + ] + ) + ) + { return jjtThis; } +} + +OCluster Cluster(): +{ Token cName; } +{ + ( + cName = {jjtThis.clusterName = cName.image.split(":")[1];} + | + cName = {jjtThis.clusterNumber = Integer.parseInt(cName.image.split(":")[1]);} + + ) + { return jjtThis; } +} + +OClusterList ClusterList(): +{ OIdentifier lastIdentifier; } +{ + ( + + [ + lastIdentifier = Identifier() { jjtThis.clusters.add(lastIdentifier); } + ( lastIdentifier = Identifier() { jjtThis.clusters.add(lastIdentifier); } )* + ] + + ) + { return jjtThis; } +} + +OMetadataIdentifier MetadataIdentifier(): +{ Token mdName; } +{ + ( + mdName = {jjtThis.name = mdName.image.split(":")[1];} + ) + { return jjtThis; } +} + +OIndexName IndexName(): +{ + StringBuilder builder = new StringBuilder(); + Token token; + OIdentifier lastIdentifier; +} +{ + ( + ( "__@recordmap@___" { builder.append("__@recordmap@___"); } )? + + lastIdentifier = Identifier() { builder.append(lastIdentifier.getValue()); } + ( + ( + { builder.append("."); } + | + { builder.append("-"); } + ) + lastIdentifier = Identifier() { builder.append(lastIdentifier.getValue()); } + )* + ) + { + jjtThis.value = builder.toString(); + return jjtThis; + } +} + + +OIndexIdentifier IndexIdentifier(): +{ + Token token; +} +{ + ( + ( + + jjtThis.indexName = IndexName() { jjtThis.type = OIndexIdentifier.Type.INDEX; } + ) + | + ( + ( + token = { jjtThis.type = OIndexIdentifier.Type.VALUES; } + | + token = { jjtThis.type = OIndexIdentifier.Type.VALUESASC; } + | + token = { jjtThis.type = OIndexIdentifier.Type.VALUESDESC; } + ) + { + jjtThis.indexNameString = token.image.split(":")[1]; + } + ) + ) + { return jjtThis; } +} + +OWhereClause WhereClause(): +{} +{ + jjtThis.baseExpression = OrBlock() + {return jjtThis;} +} + +OOrBlock OrBlock(): +{ OAndBlock lastAnd = null; } +{ + ( + lastAnd = AndBlock() { jjtThis.getSubBlocks().add(lastAnd); } + ( lastAnd = AndBlock() { jjtThis.getSubBlocks().add(lastAnd); } )* + ) + { return jjtThis; } +} + +OAndBlock AndBlock(): +{ONotBlock lastNot = null; } +{ +( + lastNot = NotBlock() { jjtThis.getSubBlocks().add(lastNot); } + ( lastNot = NotBlock() { jjtThis.getSubBlocks().add(lastNot); } )* +) { return jjtThis; } +} + +ONotBlock NotBlock(): +{} +{ +( + ( + {jjtThis.negate = true;} + ( + LOOKAHEAD( ConditionBlock() ) + jjtThis.sub = ConditionBlock() + | + LOOKAHEAD( ParenthesisBlock() ) + jjtThis.sub = ParenthesisBlock() + ) + ) + | + ( + LOOKAHEAD( ConditionBlock() ) + jjtThis.sub = ConditionBlock() + | + LOOKAHEAD( ParenthesisBlock() ) + jjtThis.sub = ParenthesisBlock() + ) +) { return jjtThis; } +} + +OBooleanExpression ParenthesisBlock(): +{} +{ + ( + jjtThis.subElement = OrBlock() + ) + { return jjtThis; } +} + +OBooleanExpression ConditionBlock(): +{OBooleanExpression result = null;} +{ +( + LOOKAHEAD( IsNotNullCondition() ) + result = IsNotNullCondition() + | + LOOKAHEAD( IsNullCondition() ) + result = IsNullCondition() + | + LOOKAHEAD( IsNotDefinedCondition() ) + result = IsNotDefinedCondition() + | + LOOKAHEAD( IsDefinedCondition() ) + result = IsDefinedCondition() + | + LOOKAHEAD( InCondition() ) + result = InCondition() + | + LOOKAHEAD( NotInCondition() ) + result = NotInCondition() + | + LOOKAHEAD( BinaryCondition() ) + result = BinaryCondition() + | + LOOKAHEAD( BetweenCondition() ) + result = BetweenCondition() + | + LOOKAHEAD( ContainsCondition() ) + result = ContainsCondition() + | + LOOKAHEAD( ContainsValueCondition() ) + result = ContainsValueCondition() + | + LOOKAHEAD( ContainsAllCondition() ) + result = ContainsAllCondition() + | + LOOKAHEAD( ContainsTextCondition() ) + result = ContainsTextCondition() + | + LOOKAHEAD( MatchesCondition() ) + result = MatchesCondition() + | + LOOKAHEAD( IndexMatchCondition() ) + result = IndexMatchCondition() + | + LOOKAHEAD( InstanceofCondition() ) + result = InstanceofCondition() + | + { result = OBooleanExpression.TRUE;} + | + { result = OBooleanExpression.FALSE;} +){ return result; } +} + +OBinaryCompareOperator CompareOperator(): +{ OBinaryCompareOperator result;} +{ +( + result = EqualsCompareOperator() + | result = LtOperator() + | result = GtOperator() + | result = NeOperator() + | result = NeqOperator() + | result = GeOperator() + | result = LeOperator() + | result = LikeOperator() + | result = ContainsKeyOperator() + | result = LuceneOperator() + | result = NearOperator() + | result = WithinOperator() + | result = ScAndOperator() + +){return result;} +} + + +OLtOperator LtOperator(): +{} +{ +( + +){return jjtThis;} +} + +OGtOperator GtOperator(): +{} +{ +( + +){return jjtThis;} +} + +ONeOperator NeOperator(): +{} +{ +( + +){return jjtThis;} +} + +ONeqOperator NeqOperator(): +{} +{ +( + +){return jjtThis;} +} + +OGeOperator GeOperator(): +{} +{ +( + +){return jjtThis;} +} + +OLeOperator LeOperator(): +{} +{ +( + +){return jjtThis;} +} + +OLikeOperator LikeOperator(): +{} +{ +( + +){return jjtThis;} +} + +OLuceneOperator LuceneOperator(): +{} +{ +( + +){ + return jjtThis; +} +} + +ONearOperator NearOperator(): +{} +{ +( + +){return jjtThis;} +} + +OWithinOperator WithinOperator(): +{} +{ +( + +){return jjtThis;} +} + +OScAndOperator ScAndOperator(): +{} +{ +( + +){return jjtThis;} +} + +OContainsKeyOperator ContainsKeyOperator(): +{} +{ +( + +){return jjtThis;} +} + +OContainsValueOperator ContainsValueOperator(): +{} +{ +( + +){return jjtThis;} +} + +OEqualsCompareOperator EqualsCompareOperator(): +{} +{ +( + { jjtThis.doubleEquals = false; } + | + { jjtThis.doubleEquals = true; } +){return jjtThis;} +} + +OBooleanExpression BinaryCondition(): +{} +{ +( + jjtThis.left = Expression() + jjtThis.operator = CompareOperator() + jjtThis.right = Expression() +){return jjtThis;} +} + +OBooleanExpression ContainsValueCondition(): +{} +{ +( + jjtThis.left = Expression() + jjtThis.operator = ContainsValueOperator() + ( + LOOKAHEAD( 3 ) + jjtThis.condition = OrBlock() + | + LOOKAHEAD( Expression() ) + jjtThis.expression = Expression() + ) +) { return jjtThis;} +} + +OBooleanExpression InstanceofCondition(): +{ + Token token; +} +{ + ( + jjtThis.left = Expression() ( + jjtThis.right = Identifier() + | + token = { jjtThis.rightString = token.image; } + | + token = { jjtThis.rightString = token.image; } + ) + ) + {return jjtThis;} +} + +OBooleanExpression IndexMatchCondition(): +{ + Token token; + jjtThis.leftExpressions = new ArrayList(); + OExpression lastExpression; +} +{ + ( + + ( + jjtThis.operator = CompareOperator() + [ + lastExpression = Expression() { jjtThis.leftExpressions.add(lastExpression); } + ( + lastExpression = Expression() { jjtThis.leftExpressions.add(lastExpression); } + )* + ] + + | + {jjtThis.between = true;} + [ + lastExpression = Expression() { jjtThis.leftExpressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtThis.leftExpressions.add(lastExpression); } + )* + ] + + [ + lastExpression = Expression() { jjtThis.rightExpressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtThis.rightExpressions.add(lastExpression); } + )* + ] + ) + ) + {return jjtThis;} +} + +OBooleanExpression BetweenCondition(): +{} +{ +( + jjtThis.first = Expression() + jjtThis.second = Expression() + jjtThis.third = Expression() +){return jjtThis;} +} + +OBooleanExpression IsNullCondition(): +{} +{ + ( + jjtThis.expression = Expression() + ) + {return jjtThis;} +} + +OBooleanExpression IsNotNullCondition(): +{} +{ +( + jjtThis.expression = Expression() +){return jjtThis;} +} + +OBooleanExpression IsDefinedCondition(): +{} +{ +( + jjtThis.expression = Expression() +){return jjtThis;} +} + +OBooleanExpression IsNotDefinedCondition(): +{} +{ +( + jjtThis.expression = Expression() +){return jjtThis;} +} + +OBooleanExpression ContainsCondition(): +{} +{ + ( + jjtThis.left = Expression() + ( + LOOKAHEAD( 3 ) + ( jjtThis.condition = OrBlock() ) + | + LOOKAHEAD( Expression() ) + jjtThis.right = Expression() + ) + ) + {return jjtThis;} +} + +OInOperator InOperator(): +{} +{ + + {return jjtThis;} +} + +OBooleanExpression InCondition(): +{ + OExpression lastExpression; +} +{ +( + jjtThis.left = Expression() + jjtThis.operator = InOperator() + ( + LOOKAHEAD(2) + ( jjtThis.rightStatement = SelectStatement() ) + | + LOOKAHEAD(2) + ( jjtThis.rightParam = InputParameter() ) + | + jjtThis.rightMathExpression = MathExpression() + ) +){return jjtThis;} +} + +OBooleanExpression NotInCondition(): +{ + OExpression lastExpression; +} +{ + ( + jjtThis.left = Expression() InOperator() + ( + LOOKAHEAD(2) + ( jjtThis.rightStatement = SelectStatement() ) + | + LOOKAHEAD(2) + ( jjtThis.rightParam = InputParameter() ) + | + jjtThis.rightMathExpression = MathExpression() + ) + ) + {return jjtThis;} +} + +OBooleanExpression ContainsAllCondition(): +{} +{ + ( + jjtThis.left = Expression() + + ( + LOOKAHEAD( 3 ) + ( jjtThis.rightBlock = OrBlock() ) + | + LOOKAHEAD( Expression() ) + jjtThis.right = Expression() + ) + ) + {return jjtThis;} +} + +OBooleanExpression ContainsTextCondition(): +{} +{ + ( + jjtThis.left = Expression() jjtThis.right = Expression() + ) + {return jjtThis;} +} + +OBooleanExpression MatchesCondition(): +{Token token;} +{ + ( + jjtThis.expression = Expression() + ( + ( token = {jjtThis.right = token.image;} ) + | + ( token = {jjtThis.right = token.image;} ) + | + ( jjtThis.rightParam = InputParameter() ) + ) + ) + + {return jjtThis;} +} + +OOrderBy OrderBy(): +{ + jjtThis.items = new java.util.ArrayList(); + OOrderByItem lastItem; + OIdentifier lastIdentifier; + OModifier lastModifier; + ORid lastRid; + Token lastToken; +} +{ +( + + ( + ( + ( + { + lastItem = new OOrderByItem(); + jjtThis.items.add(lastItem); + } + ( + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + [ lastModifier = Modifier() { lastItem.modifier = lastModifier; } ] + ) + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + ) + | + ( + + ( + { + lastItem = new OOrderByItem(); + jjtThis.items.add(lastItem); + } + ( + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + [ lastModifier = Modifier() { lastItem.modifier = lastModifier; } ] + ) + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + + ) + ) + ( + "," + ( + ( + ( + { + lastItem = new OOrderByItem(); + jjtThis.items.add(lastItem); + } + ( + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + [ lastModifier = Modifier() { lastItem.modifier = lastModifier; } ] + ) + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + ) + | + ( + + ( + { + lastItem = new OOrderByItem(); + jjtThis.items.add(lastItem); + } + ( + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + [ lastModifier = Modifier() { lastItem.modifier = lastModifier; } ] + ) + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + + ) + ) + )* +) {return jjtThis;} +} + +OGroupBy GroupBy(): +{ OExpression lastExpression; } +{ +( + lastExpression = Expression() { jjtThis.items.add(lastExpression); } + ( + "," + lastExpression = Expression() { jjtThis.items.add(lastExpression); } + )* +) {return jjtThis;} +} + +OUnwind Unwind(): +{ OIdentifier lastIdentifier; } +{ +( + lastIdentifier = Identifier() { jjtThis.items.add(lastIdentifier); } + ( + "," + lastIdentifier = Identifier() { jjtThis.items.add(lastIdentifier); } + )* +) {return jjtThis;} +} + + +OLimit Limit(): +{} +{ + ( + + ( + jjtThis.num = Integer() + | + jjtThis.inputParam = InputParameter() + ) + ) + { return jjtThis; } +} + +OSkip Skip(): +{ } +{ + ( + ( + + ( + jjtThis.num = Integer() + | + jjtThis.inputParam = InputParameter() + ) + ) + | + ( + + ( + jjtThis.num = Integer() + | + jjtThis.inputParam = InputParameter() + ) + ) + ) {return jjtThis;} +} + +OBatch Batch(): +{} +{ + ( + + ( + jjtThis.num = Integer() + | + jjtThis.inputParam = InputParameter() + ) + ) + { return jjtThis; } +} + +OTimeout Timeout(): +{ OInteger val; } +{ + + + ( + val = Integer() { jjtThis.val = val.getValue(); } + [ + ( { jjtThis.failureStrategy = OTimeout.RETURN;} ) + | + ( { jjtThis.failureStrategy = OTimeout.EXCEPTION;} ) + ] + ) + { return jjtThis; } +} + + +java.lang.Number Wait(): +{ OInteger val; } +{ + ( + val = Integer() + ) + { return val.getValue(); } +} + + +java.lang.Number Retry(): +{ OInteger val; } +{ + ( + val = Integer() + ) + { return val.getValue(); } +} + + + + + + + +OCollection Collection(): +{ + OExpression lastExpression; +} +{ + ( + + + [ + lastExpression = Expression() { jjtThis.expressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtThis.expressions.add(lastExpression); } + )* + ] + + ) + { return jjtThis; } +} + + + +OFetchPlan FetchPlan(): +{ OFetchPlanItem lastItem; } +{ + ( + lastItem = FetchPlanItem() { jjtThis.items.add(lastItem); } + ( lastItem = FetchPlanItem() { jjtThis.items.add(lastItem); } )* + ) + { return jjtThis; } +} + +OFetchPlanItem FetchPlanItem(): +{ OIdentifier lastIdentifier; + boolean lastStarred = false; +} +{ + ( + ( + { jjtThis.star = true; } + | + [ + + ( + jjtThis.leftDepth = Integer() + | + { jjtThis.leftStar = true; } + ) + + ] + lastIdentifier = Identifier() { lastStarred = false; } [ { lastStarred = true; }] + { + String field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtThis.fieldChain.add(field); + } + ( + lastIdentifier = Identifier() { lastStarred = false; } [ { lastStarred = true; } ] + { + field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtThis.fieldChain.add(field); + } + )* + ) + jjtThis.rightDepth = Integer() + ) + { return jjtThis; } +} + + + +OTraverseProjectionItem TraverseProjectionItem(): +{} +{ + ( + jjtThis.base = BaseIdentifier() + [ LOOKAHEAD( Modifier() ) jjtThis.modifier = Modifier() ] + ) + { return jjtThis; } +} + +OJson Json(): +{ + OJsonItem lastItem; + Token token; +} +{ + ( + + [ + { lastItem = new OJsonItem(); } + ( + lastItem.leftIdentifier = Identifier() + | + token = {lastItem.leftString = token.image; } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + ) + + lastItem.right = Expression() { jjtThis.items.add(lastItem); } + ( + + { lastItem = new OJsonItem(); } + ( + lastItem.leftIdentifier = Identifier() + | + token = {lastItem.leftString = token.image; } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + ) + + lastItem.right = Expression() { jjtThis.items.add(lastItem); } + )* + ] + + ) + {return jjtThis;} +} + + + +OMatchExpression MatchExpression(): +{ OMatchPathItem nextItem = null; } +{ + ( + jjtThis.origin = MatchFilter() + ( + ( + LOOKAHEAD(3) + nextItem = MatchPathItem() + | + LOOKAHEAD(3) + nextItem = MultiMatchPathItemArrows() + | + LOOKAHEAD(3) + nextItem = MultiMatchPathItem() + | + LOOKAHEAD(OutPathItem()) + nextItem = OutPathItem() + | + nextItem = InPathItem() + | + LOOKAHEAD(BothPathItem()) + nextItem = BothPathItem() + ) + { jjtThis.items.add(nextItem); } + )* + ) { return jjtThis; } +} + + +OMatchPathItem MatchPathItem(): +{} +{ + ( + jjtThis.method = MethodCall() + [ jjtThis.filter = MatchFilter() ] + ){ return jjtThis; } +} + +OMatchPathItem MatchPathItemFirst(): +{} +{ + ( + jjtThis.function = FunctionCall() + [ jjtThis.filter = MatchFilter() ] + ){ return jjtThis; } +} + +OMatchPathItem MultiMatchPathItem(): +{ OMatchPathItem nextItem = null; } +{ + ( + + + ( + nextItem = MatchPathItemFirst() { jjtThis.items.add(nextItem); } + ) + ( + LOOKAHEAD(MatchPathItem()) + nextItem = MatchPathItem() { jjtThis.items.add(nextItem); } + )* + + [ jjtThis.filter = MatchFilter() ] + ){ return jjtThis; } +} + +OMatchPathItem MultiMatchPathItemArrows(): +{ + OMatchPathItem prevItem = null; + OMatchPathItem nextItem = null; +} +{ + ( + + + ( + ( + LOOKAHEAD( OutPathItemOpt() ) + nextItem = OutPathItemOpt() { jjtThis.items.add(nextItem); } + | + LOOKAHEAD( InPathItemOpt() ) + nextItem = InPathItemOpt() { jjtThis.items.add(nextItem); } + | + LOOKAHEAD( BothPathItemOpt() ) + nextItem = BothPathItemOpt() { jjtThis.items.add(nextItem); } + ){ + if(prevItem !=null && prevItem.filter == null){ + throw new OQueryParsingException("MATCH sub-pattern with no square brackets"); + } + prevItem = nextItem; + } + )+ + + [ jjtThis.filter = MatchFilter() ] + ){ return jjtThis; } +} + +OMatchFilter MatchFilter(): +{ OMatchFilterItem lastItem = null; } +{ + ( + + [ + lastItem = MatchFilterItem() { jjtThis.items.add(lastItem); } + ( + + lastItem = MatchFilterItem() { jjtThis.items.add(lastItem); } + )* + ] + + ) { return jjtThis; } +} + +OMatchFilterItem MatchFilterItem(): +{} +{ + ( + ( + jjtThis.className = Expression() + ) + | + ( + jjtThis.rid = Rid() + ) + | + ( + jjtThis.classNames = Expression() + ) + | + ( + jjtThis.alias = Identifier() + ) + | + ( + + ( + jjtThis.filter = WhereClause() + ) + + ) + | + ( + + ( + jjtThis.whileCondition = WhereClause() + ) + + ) + | + ( + jjtThis.maxDepth = Integer() + ) + | + ( + + ( + { jjtThis.optional = true; } + | + { jjtThis.optional = false; } + ) + ) + ) + { return jjtThis; } +} + +OMatchPathItem OutPathItem(): +{ OIdentifier edgeName = null; } +{ + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + + jjtThis.filter = MatchFilter() + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "out"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + +OMatchPathItem InPathItem(): +{ OIdentifier edgeName = null; } +{ + + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + jjtThis.filter = MatchFilter() + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "in"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + +OMatchPathItem BothPathItem(): +{ OIdentifier edgeName = null; } +{ + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + jjtThis.filter = MatchFilter() + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "both"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + + +OMatchPathItem OutPathItemOpt(): +{ OIdentifier edgeName = null; } +{ + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + + [jjtThis.filter = MatchFilter()] + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "out"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + +OMatchPathItem InPathItemOpt(): +{ OIdentifier edgeName = null; } +{ + + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + [jjtThis.filter = MatchFilter()] + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "in"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + +OMatchPathItem BothPathItemOpt(): +{ OIdentifier edgeName = null; } +{ + + ( + ( + + [edgeName = Identifier()] + + ) + | + + ) + [jjtThis.filter = MatchFilter()] + + { + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtThis.method = new OMethodCall(-1); + jjtThis.method.methodName = new OIdentifier(-1); + jjtThis.method.methodName.value = "both"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtThis.method.params.add(exp); + return jjtThis; + } +} + +OProfileStorageStatement ProfileStorageStatement(): +{} +{ + + ( + {jjtThis.on = true;} + | + {jjtThis.on = false;} + ) + {return jjtThis;} +} + +OTruncateClassStatement TruncateClassStatement(): +{} +{ + + jjtThis.className = Identifier() + [ {jjtThis.polymorphic = true;} ] + [ {jjtThis.unsafe = true;} ] + { return jjtThis; } +} + +OTruncateClusterStatement TruncateClusterStatement(): +{} +{ + + ( + jjtThis.clusterName = Identifier() + | + jjtThis.clusterNumber = Integer() + ) + [ {jjtThis.unsafe = true;} ] + { return jjtThis; } +} + +OTruncateRecordStatement TruncateRecordStatement(): +{ ORid lastRecord; } +{ + + ( + jjtThis.record = Rid() + | + ( + { jjtThis.records = new ArrayList(); } + [ + lastRecord = Rid() { jjtThis.records.add(lastRecord); } + ( + + lastRecord = Rid() { jjtThis.records.add(lastRecord); } + )* + ] + + ) + ) + { return jjtThis; } +} + + +OFindReferencesStatement FindReferencesStatement(): +{ SimpleNode lastTarget; } +{ + + ( + jjtThis.rid = Rid() + | + ( + + jjtThis.subQuery = StatementInternal() + + ) + ) + [ + { jjtThis.targets = new ArrayList(); } + ( + LOOKAHEAD(IndexIdentifier()) + lastTarget = IndexIdentifier() + | + lastTarget = Identifier() + ){ jjtThis.targets.add(lastTarget); } + ( + + ( + lastTarget = Identifier() + | + lastTarget = Cluster() + ){ jjtThis.targets.add(lastTarget); } + )* + + ] + { return jjtThis; } +} + +OCreateClassStatement CreateClassStatement(): +{ + OIdentifier lastIdentifier; + OInteger lastInteger; +} +{ + + ( + jjtThis.name = Identifier() + [ {jjtThis.ifNotExists = true;} ] + [ + lastIdentifier = Identifier() { jjtThis.superclasses = new ArrayList(); jjtThis.superclasses.add(lastIdentifier); } + ( + + lastIdentifier = Identifier() { jjtThis.superclasses.add(lastIdentifier); } + )* + ] + [ + lastInteger = Integer() { jjtThis.clusters = new ArrayList(); jjtThis.clusters.add(lastInteger); } + ( + + lastInteger = Integer() { jjtThis.clusters.add(lastInteger); } + )* + ] + [ jjtThis.totalClusterNo = Integer() ] + [ { jjtThis.abstractClass = true; } ] + ) + { return jjtThis; } +} + +OAlterClassStatement AlterClassStatement(): +{ + OIdentifier lastIdentifier; + OInteger lastInteger; + Token lastToken; +} +{ + + jjtThis.name = Identifier() + ( + + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.NAME; } + jjtThis.identifierValue = Identifier() + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SHORTNAME; } + ( + jjtThis.identifierValue = Identifier() + | + + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SUPERCLASS; } + [ + ( + {jjtThis.add = true;} + ) + | + ( + {jjtThis.remove = true;} + ) + ] + ( + jjtThis.identifierValue = Identifier() + | + { jjtThis.identifierValue = null; } + ) + ) + | + ( + { + jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SUPERCLASSES; + jjtThis.identifierListValue = new ArrayList(); + } + ( + ( + lastIdentifier = Identifier() { jjtThis.identifierListValue.add(lastIdentifier); } + ( + + lastIdentifier = Identifier() { jjtThis.identifierListValue.add(lastIdentifier); } + )* + ) + | + { jjtThis.identifierListValue = null; } + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.OVERSIZE; } + jjtThis.numberValue = Number() + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.STRICTMODE; } + ( + jjtThis.expression = Expression() + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ADDCLUSTER; } + ( + jjtThis.identifierValue = Identifier() + | + jjtThis.numberValue = Integer() + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.REMOVECLUSTER; } + ( + jjtThis.identifierValue = Identifier() + | + jjtThis.numberValue = Integer() + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.CUSTOM; } + jjtThis.customKey = Identifier() + [ + + jjtThis.customValue = Expression() + ] + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ABSTRACT; } + ( + jjtThis.expression = Expression() + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.CLUSTERSELECTION; } + ( + jjtThis.identifierValue = Identifier() + | + "round-robin" { jjtThis.customString = "round-robin"; } + | + lastToken = { jjtThis.customString = token.image.substring(1, token.image.length() - 1); } + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.DESCRIPTION; } + ( + jjtThis.expression = Expression() + ) + ) + | + ( + { jjtThis.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ENCRYPTION; } + ( + jjtThis.identifierValue = Identifier() + | + + ) + ) + ) + [ { jjtThis.unsafe = true; } ] + { return jjtThis; } +} + + +ODropClassStatement DropClassStatement(): +{} +{ + + jjtThis.name = Identifier() + [ { jjtThis.ifExists = true; } ] + [ { jjtThis.unsafe = true; } ] + { return jjtThis; } +} + +void IfNotExists():{} +{ + +} +OCreatePropertyStatement CreatePropertyStatement(): +{ + OCreatePropertyAttributeStatement lastAttribute; +} +{ + + ( + jjtThis.className = Identifier() + + jjtThis.propertyName = Identifier() + + [ LOOKAHEAD(3) IfNotExists() { jjtThis.ifNotExists = true; }] + + jjtThis.propertyType = Identifier() + [ + jjtThis.linkedType = Identifier() + ] + [ + + lastAttribute = CreatePropertyAttributeStatement() { jjtThis.attributes.add(lastAttribute); } + ( lastAttribute = CreatePropertyAttributeStatement() { jjtThis.attributes.add(lastAttribute); })* + + ] + [ { jjtThis.unsafe = true; } ] + ) + { return jjtThis; } +} + +OCreatePropertyAttributeStatement CreatePropertyAttributeStatement(): +{ +} +{ + ( + ( + jjtThis.settingName = Identifier() + [LOOKAHEAD( { getToken(1).kind != COMMA && getToken(1).kind != RPAREN} ) + jjtThis.settingValue = Expression() + ] + ) + ) + + { return jjtThis; } +} + +OAlterPropertyStatement AlterPropertyStatement(): +{} +{ + + ( + jjtThis.className = Identifier() + + jjtThis.propertyName = Identifier() + ( + LOOKAHEAD(3) + ( + + jjtThis.customPropertyName = Identifier() + + jjtThis.customPropertyValue = Expression() + ) + | + ( + jjtThis.settingName = Identifier() + jjtThis.settingValue = Expression() + { + if(jjtThis.settingName.getStringValue().equalsIgnoreCase("custom") && jjtThis.settingValue.toString().equalsIgnoreCase("clear")){ + jjtThis.settingName = null; + jjtThis.settingValue = null; + jjtThis.clearCustom = true; + } + } + ) + ) + ) + { return jjtThis; } +} + +ODropPropertyStatement DropPropertyStatement(): +{} +{ + + jjtThis.className = Identifier() + + jjtThis.propertyName = Identifier() + [ { jjtThis.ifExists = true; } ] + [ { jjtThis.force = true; }] + { return jjtThis; } +} + +OCreateIndexStatement CreateIndexStatement(): +{ + OCreateIndexStatement.Property lastProperty; + OIdentifier lastIdentifier; + ORecordAttribute lastRecordAttr; +} +{ + + + jjtThis.name = IndexName() + ( + LOOKAHEAD(3) + ( + + jjtThis.className = Identifier() + + ( + lastIdentifier = Identifier() { + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.name = lastIdentifier; + jjtThis.propertyList.add(lastProperty); + } + | + lastRecordAttr = RecordAttribute() { + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.recordAttribute = lastRecordAttr; + jjtThis.propertyList.add(lastProperty); + } + ) + [ + + ( + { lastProperty.byKey = true; } + | + { lastProperty.byValue = true; } + ) + ] + [ + + lastProperty.collate = Identifier() + ] + ( + + ( + lastIdentifier = Identifier() { + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.name = lastIdentifier; + jjtThis.propertyList.add(lastProperty); + } + | + lastRecordAttr = RecordAttribute() { + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.recordAttribute = lastRecordAttr; + jjtThis.propertyList.add(lastProperty); + } + ) + [ + + ( + { lastProperty.byKey = true; } + | + { lastProperty.byValue = true; } + ) + ] + [ + + lastProperty.collate = Identifier() + ] + )* + + + + jjtThis.type = Identifier() + ) + | + jjtThis.type = Identifier() + ) + + ( + LOOKAHEAD(2) + ( + jjtThis.engine = Identifier() + [ + LOOKAHEAD(2) + ( + jjtThis.metadata = Json() + ) + | + ( + lastIdentifier = Identifier() {jjtThis.keyTypes.add(lastIdentifier);} + ( + lastIdentifier = Identifier() {jjtThis.keyTypes.add(lastIdentifier);} + )* + [ jjtThis.metadata = Json() ] + ) + ] + ) + | + ( + [ + LOOKAHEAD(2) + ( + jjtThis.metadata = Json() + ) + | + ( + lastIdentifier = Identifier() {jjtThis.keyTypes.add(lastIdentifier);} + ( + lastIdentifier = Identifier() {jjtThis.keyTypes.add(lastIdentifier);} + )* + [ jjtThis.metadata = Json() ] + ) + ] + ) + ) + + { return jjtThis; } +} + +ORebuildIndexStatement RebuildIndexStatement(): +{} +{ + ( + + ( + jjtThis.name = IndexName() + | + { jjtThis.all = true; } + ) + ) + { return jjtThis; } +} + +ODropIndexStatement DropIndexStatement(): +{} +{ + ( + + ( + jjtThis.name = IndexName() + | + { jjtThis.all = true; } + ) + ) + { return jjtThis; } +} + + +OCreateClusterStatement CreateClusterStatement(): +{} +{ + ( + + ( + ( ) + | + ( { jjtThis.blob = true; } ) + ) + jjtThis.name = Identifier() + [ jjtThis.id = Integer() ] + ) + { return jjtThis; } +} + +OAlterClusterStatement AlterClusterStatement(): +{} +{ + + ( + jjtThis.name = Identifier() + ) + [ { jjtThis.starred = true; }] + jjtThis.attributeName = Identifier() + jjtThis.attributeValue = Expression() + { return jjtThis; } +} + +ODropClusterStatement DropClusterStatement(): +{} +{ + + ( + jjtThis.name = Identifier() + | + jjtThis.id = Integer() + ) + { return jjtThis; } +} + +OAlterDatabaseStatement AlterDatabaseStatement(): +{} +{ + + ( + LOOKAHEAD(3) + ( + + jjtThis.customPropertyName = Identifier() + + jjtThis.customPropertyValue = Expression() + ) + | + ( + jjtThis.settingName = Identifier() + jjtThis.settingValue = Expression() + ) + ) + { return jjtThis; } +} + +OCommandLineOption CommandLineOption(): +{} +{ + ( + jjtThis.name = Identifier() + ) + {return jjtThis;} +} + +OOptimizeDatabaseStatement OptimizeDatabaseStatement(): +{ OCommandLineOption lastOption; } +{ + ( + + ( + lastOption = CommandLineOption() { jjtThis.options.add(lastOption); } + )* + ) + {return jjtThis;} +} + +OCreateLinkStatement CreateLinkStatement(): +{ } +{ + ( + + jjtThis.name = Identifier() + + jjtThis.type = Identifier() + + jjtThis.sourceClass = Identifier() + + ( + jjtThis.sourceField = Identifier() + | + jjtThis.sourceRecordAttr = RecordAttribute() + ) + + jjtThis.destClass = Identifier() + + ( + jjtThis.destField = Identifier() + | + jjtThis.destRecordAttr = RecordAttribute() + ) + [ { jjtThis.inverse = true; } ] + ) + {return jjtThis;} +} + +OExplainStatement ExplainStatement(): +{} +{ + ( + + jjtThis.statement = StatementInternal() + ) + { return jjtThis; } +} + +OPermission Permission(): +{} +{ + ( + { jjtThis.permission = "CREATE"; } + | + { jjtThis.permission = "READ"; } + | + { jjtThis.permission = "UPDATE"; } + | + { jjtThis.permission = "DELETE"; } + | + { jjtThis.permission = "EXECUTE"; } + | + { jjtThis.permission = "ALL"; } + | + { jjtThis.permission = "NONE"; } + ) + {return jjtThis;} +} + +OResourcePathItem ResourcePathItem(): +{} +{ + ( + { jjtThis.name = "cluster"; } + | + { jjtThis.star = true; } + | + jjtThis.identifier = Identifier() + ) + {return jjtThis;} +} + +OGrantStatement GrantStatement(): +{ + OResourcePathItem lastItem; +} +{ + ( + + jjtThis.permission = Permission() + + lastItem = ResourcePathItem() { jjtThis.resourceChain.add(lastItem); } + ( + + lastItem = ResourcePathItem() { jjtThis.resourceChain.add(lastItem); } + )* + + jjtThis.actor = Identifier() + ) + { return jjtThis; } +} + +ORevokeStatement RevokeStatement(): +{ + OResourcePathItem lastItem; +} +{ + ( + + jjtThis.permission = Permission() + + lastItem = ResourcePathItem() { jjtThis.resourceChain.add(lastItem); } + ( + + lastItem = ResourcePathItem() { jjtThis.resourceChain.add(lastItem); } + )* + + jjtThis.actor = Identifier() + ) + { return jjtThis; } +} + +OCreateFunctionStatement CreateFunctionStatement(): +{ + Token token; + OIdentifier lastIdentifier; +} +{ + ( + + jjtThis.name = Identifier() + token = { + jjtThis.codeQuoted = token.image; + jjtThis.code = token.image.substring(1, token.image.length() -1); + } + [ + + + lastIdentifier = Identifier() { + jjtThis.parameters = new ArrayList(); + jjtThis.parameters.add(lastIdentifier); + } + ( + + lastIdentifier = Identifier() { jjtThis.parameters.add(lastIdentifier); } + )* + + ] + [ + + ( + { jjtThis.idempotent = true; } + | + { jjtThis.idempotent = false; } + ) + ] + [ + + jjtThis.language = Identifier() + ] + ) + { return jjtThis; } +} + + +OLetStatement LetStatement(): +{ } +{ + ( + + jjtThis.name = Identifier() + + ( + LOOKAHEAD(Statement()) + jjtThis.statement = StatementInternal() + | + LOOKAHEAD(Expression()) + jjtThis.expression = Expression() + ) + ) + {return jjtThis;} +} + +OBeginStatement BeginStatement(): +{ } +{ + ( + + [ jjtThis.isolation = Identifier() ] + ) + {return jjtThis;} +} + +OCommitStatement CommitStatement(): +{ } +{ + ( + + [ + + jjtThis.retry = Integer() + ] + ) + {return jjtThis;} +} + +ORollbackStatement RollbackStatement(): +{ } +{ + ( + + ) + {return jjtThis;} +} + +OReturnStatement ReturnStatement(): +{ } +{ + ( + + [ + jjtThis.expression = Expression() + ] + ) + {return jjtThis;} +} + +OIfStatement IfStatement(): +{ OStatement last; } +{ + ( + + jjtThis.expression = OrBlock() + + ( + LOOKAHEAD(StatementSemicolon()) + last = StatementSemicolon() { jjtThis.statements.add(last); } + | + last = IfStatement() { jjtThis.statements.add(last); } + | + + )* + + ) + { return jjtThis; } +} + +OSleepStatement SleepStatement(): +{ } +{ + ( + jjtThis.millis = Integer() + ) + { return jjtThis; } +} + +OConsoleStatement ConsoleStatement(): +{ } +{ + ( + + jjtThis.logLevel = Identifier() + jjtThis.message = Expression() + ) + { return jjtThis; } +} + + +OCreateSequenceStatement CreateSequenceStatement(): +{ + OIdentifier lastIdentifier; +} +{ + ( + + jjtThis.name = Identifier() + + lastIdentifier = Identifier(){ + if(lastIdentifier.getStringValue().equalsIgnoreCase("cached")){ + jjtThis.type = OCreateSequenceStatement.TYPE_CACHED; + }else if(lastIdentifier.getStringValue().equalsIgnoreCase("ordered")){ + jjtThis.type = OCreateSequenceStatement.TYPE_ORDERED; + }else{ + throw new ParseException(); + } + } + + [ jjtThis.start = Expression() ] + [ jjtThis.increment = Expression() ] + [ jjtThis.cache = Expression() ] + ) + { return jjtThis; } +} + +OAlterSequenceStatement AlterSequenceStatement(): +{ + OIdentifier lastIdentifier; +} +{ + ( + + jjtThis.name = Identifier() + [ jjtThis.start = Expression() ] + [ jjtThis.increment = Expression() ] + [ jjtThis.cache = Expression() ] + ) + { return jjtThis; } +} + + +ODropSequenceStatement DropSequenceStatement(): +{ + OIdentifier lastIdentifier; +} +{ + ( + + jjtThis.name = Identifier() + ) + { return jjtThis; } +} + + + +OHaStatusStatement HaStatusStatement(): +{ + Token token; +} +{ + ( + + ( + token = "-servers" { jjtThis.servers = true; } + | + token = "-db" { jjtThis.db = true; } + | + token = "-latency" { jjtThis.latency = true; } + | + token = "-messages" { jjtThis.messages = true; } + | + token = "-all" { + jjtThis.servers = true; + jjtThis.db = true; + jjtThis.latency = true; + jjtThis.messages = true; + } + | + token = "-output=text" { jjtThis.outputText = true; } + )* + ) + { return jjtThis; } +} + +OHaRemoveServerStatement HaRemoveServerStatement(): +{} +{ + ( + + jjtThis.serverName = Identifier() + ) + { return jjtThis; } +} + +OHaSyncDatabaseStatement HaSyncDatabaseStatement(): +{} +{ + ( + + [ "-force" { jjtThis.force = true; } ] + [ "-full" { jjtThis.full = true; } ] + ) + { return jjtThis; } +} + +OHaSyncClusterStatement HaSyncClusterStatement(): +{} +{ + ( + + jjtThis.clusterName = Identifier() + [ + ( + "-full_replace" {jjtThis.modeFull = true;} + ) + | + ( + "-merge" {jjtThis.modeMerge = true;} + ) + ] + ) + { return jjtThis; } +} \ No newline at end of file diff --git a/core/src/main/java-templates/com/orientechnologies/orient/core/OConstants.java b/core/src/main/java-templates/com/orientechnologies/orient/core/OConstants.java new file mode 100644 index 00000000000..ac78bc177b1 --- /dev/null +++ b/core/src/main/java-templates/com/orientechnologies/orient/core/OConstants.java @@ -0,0 +1,71 @@ +/* + * + * * Copyright 2010-2016 OrientDB LTD (http://orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ +package com.orientechnologies.orient.core; + +public class OConstants { + public static final String ORIENT_URL = "https://www.orientdb.com"; + public static final String COPYRIGHT = "Copyrights (c) 2017 OrientDB LTD"; + public static final String ORIENT_VERSION = "${project.version}"; + public static final String GROUPID = "${project.groupId}"; + public static final String ARTIFACTID = "${project.artifactId}"; + public static final String REVISION = "${buildNumber}"; + //deprecated properties + public static final int ORIENT_VERSION_MAJOR = 2; + public static final int ORIENT_VERSION_MINOR = 2; + public static final int ORIENT_VERSION_HOFIX = 18; + + /** + * Returns the complete text of the current OrientDB version. + */ + public static String getVersion() { + final StringBuilder buffer = new StringBuilder(); + buffer.append(OConstants.ORIENT_VERSION); + buffer.append(" (build "); + buffer.append(OConstants.REVISION); + buffer.append(")"); + + return buffer.toString(); + } + + /** + * Returns current OrientDB version as array with 3 integers: major, minor and hotfix numbers. Example: [3,0,0]. + * This method is deprecated and will be removed in the future + */ + @Deprecated + public static int[] getVersionNumber() { + return new int[] { ORIENT_VERSION_MAJOR, ORIENT_VERSION_MINOR, ORIENT_VERSION_HOFIX }; + } + + /** + * Returns true if current OrientDB version is a snapshot. + */ + public static boolean isSnapshot() { + return ORIENT_VERSION.endsWith("SNAPSHOT"); + } + + /** + * Returns the build number if any. + * + * @return + */ + public static String getBuildNumber() { + return OConstants.REVISION; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OCollection.java b/core/src/main/java/com/orientechnologies/common/collection/OCollection.java new file mode 100755 index 00000000000..bf5362ef3a3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OCollection.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import com.orientechnologies.common.util.OSizeable; + +/** + * If class implements given interface it means that this class represents collection which is not part of Java Collections + * Framework. + * + * @param + * Collection item type. + */ +public interface OCollection extends Iterable, OSizeable { + + void add(T value); + + void remove(T value); +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OIterableObject.java b/core/src/main/java/com/orientechnologies/common/collection/OIterableObject.java new file mode 100644 index 00000000000..2cd8517eb66 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OIterableObject.java @@ -0,0 +1,74 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.collection; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.orientechnologies.common.util.OResettable; + +/** + * Allows to iterate over a single object + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OIterableObject implements Iterable, OResettable, Iterator { + + private final T object; + private boolean alreadyRead = false; + + public OIterableObject(T o) { + object = o; + } + + /** + * Returns an iterator over a set of elements of type T. + * + * @return an Iterator. + */ + public Iterator iterator() { + return this; + } + + @Override + public void reset() { + alreadyRead = false; + } + + @Override + public boolean hasNext() { + return !alreadyRead; + } + + @Override + public T next() { + if (!alreadyRead) { + alreadyRead = true; + return object; + } else + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OIterableObjectArray.java b/core/src/main/java/com/orientechnologies/common/collection/OIterableObjectArray.java new file mode 100644 index 00000000000..483a0118af2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OIterableObjectArray.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.collection; + +import java.lang.reflect.Array; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Allow to iterate over the array casted to Object. + * + * @author Anton Cherneckiy (pesua.mail--at--gmail.com) + */ +public class OIterableObjectArray implements Iterable { + + private final Object object; + private int length; + + public OIterableObjectArray(Object o) { + object = o; + length = Array.getLength(o); + } + + /** + * Returns an iterator over a set of elements of type T. + * + * @return an Iterator. + */ + public Iterator iterator() { + return new ObjIterator(); + } + + private class ObjIterator implements Iterator { + private int p = 0; + + /** + * Returns true if the iteration has more elements. (In other words, returns true if next would + * return an element rather than throwing an exception.) + * + * @return true if the iterator has more elements. + */ + public boolean hasNext() { + return p < length; + } + + /** + * Returns the next element in the iteration. + * + * @return the next element in the iteration. + * @throws java.util.NoSuchElementException + * iteration has no more elements. + */ + @SuppressWarnings("unchecked") + public T next() { + if (p < length) { + return (T) Array.get(object, p++); + } else { + throw new NoSuchElementException(); + } + } + + /** + * Removes from the underlying collection the last element returned by the iterator (optional operation). This method can be + * called only once per call to next. The behavior of an iterator is unspecified if the underlying collection is + * modified while the iteration is in progress in any way other than by calling this method. + * + * @throws UnsupportedOperationException + * if the remove operation is not supported by this Iterator. + * @throws IllegalStateException + * if the next method has not yet been called, or the remove method has already been called after + * the last call to the next method. + */ + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OLRUCache.java b/core/src/main/java/com/orientechnologies/common/collection/OLRUCache.java new file mode 100755 index 00000000000..1511c8e080b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OLRUCache.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The most simpler LRU cache implementation in Java. + */ +public class OLRUCache extends LinkedHashMap { + + private static final long serialVersionUID = 0; + + final private int cacheSize; + + public OLRUCache(final int iCacheSize) { + super(16, (float) 0.75, true); + this.cacheSize = iCacheSize; + } + + protected boolean removeEldestEntry(final Map.Entry eldest) { + return size() >= cacheSize; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OLazyIterator.java b/core/src/main/java/com/orientechnologies/common/collection/OLazyIterator.java new file mode 100644 index 00000000000..d8cc8d68068 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OLazyIterator.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.Iterator; + +/** + * Generic interface for lazy iterators allowing the update of current value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OLazyIterator extends Iterator { + public T update(T iValue); +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OLazyIteratorListWrapper.java b/core/src/main/java/com/orientechnologies/common/collection/OLazyIteratorListWrapper.java new file mode 100644 index 00000000000..cc7f6c8379a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OLazyIteratorListWrapper.java @@ -0,0 +1,53 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.ListIterator; + +/** + * Lazy iterator implementation based on List Iterator. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OLazyIteratorListWrapper implements OLazyIterator { + private ListIterator underlying; + + public OLazyIteratorListWrapper(ListIterator iUnderlying) { + underlying = iUnderlying; + } + + public boolean hasNext() { + return underlying.hasNext(); + } + + public T next() { + return underlying.next(); + } + + public void remove() { + underlying.remove(); + } + + public T update(T e) { + underlying.set(e); + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OMultiCollectionIterator.java b/core/src/main/java/com/orientechnologies/common/collection/OMultiCollectionIterator.java new file mode 100755 index 00000000000..1b63efb881c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OMultiCollectionIterator.java @@ -0,0 +1,300 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.common.util.OSupportsContains; +import com.orientechnologies.orient.core.db.record.OAutoConvertToRecord; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.iterator.OLazyWrapperIterator; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Iterator that allow to iterate against multiple collection of elements. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OMultiCollectionIterator implements Iterator, Iterable, OResettable, OSizeable, OSupportsContains, + OAutoConvertToRecord { + private List sources; + private Iterator sourcesIterator; + private Iterator partialIterator; + + private int browsed = 0; + private int skip = -1; + private int limit = -1; + private boolean embedded = false; + private boolean autoConvert2Record = true; + + + private int skipped = 0; + + public OMultiCollectionIterator() { + sources = new ArrayList(); + } + + public OMultiCollectionIterator(final Iterator> iterator) { + sourcesIterator = iterator; + getNextPartial(); + } + + @Override + public boolean hasNext() { + while(skipped < skip){ + if(!hasNextInternal()){ + return false; + } + partialIterator.next(); + skipped++; + } + return hasNextInternal(); + } + + private boolean hasNextInternal() { + if (sourcesIterator == null) { + if (sources == null || sources.isEmpty()) + return false; + + // THE FIRST TIME CREATE THE ITERATOR + sourcesIterator = sources.iterator(); + getNextPartial(); + } + + if (partialIterator == null) + return false; + + if (limit > -1 && browsed >= limit) + return false; + + if (partialIterator.hasNext()) + return true; + else if (sourcesIterator.hasNext()) + return getNextPartial(); + + return false; + } + + @Override + public T next() { + if (!hasNext()) + throw new NoSuchElementException(); + + browsed++; + return partialIterator.next(); + } + + @Override + public Iterator iterator() { + reset(); + return this; + } + + @Override + public void reset() { + sourcesIterator = null; + partialIterator = null; + browsed = 0; + skipped = 0; + } + + public OMultiCollectionIterator add(final Object iValue) { + if (iValue != null) { + if (sourcesIterator != null) + throw new IllegalStateException("MultiCollection iterator is in use and new collections cannot be added"); + + if (iValue instanceof OAutoConvertToRecord) + ((OAutoConvertToRecord) iValue).setAutoConvertToRecord(autoConvert2Record); + + sources.add(iValue); + } + return this; + } + + public int size() { + // SUM ALL THE COLLECTION SIZES + int size = 0; + final int totSources = sources.size(); + for (int i = 0; i < totSources; ++i) { + final Object o = sources.get(i); + + if (o != null) + if (o instanceof Collection) + size += ((Collection) o).size(); + else if (o instanceof Map) + size += ((Map) o).size(); + else if (o instanceof OSizeable) + size += ((OSizeable) o).size(); + else if (o.getClass().isArray()) + size += Array.getLength(o); + else if (o instanceof Iterator && o instanceof OResettable) { + while (((Iterator) o).hasNext()) { + size++; + ((Iterator) o).next(); + } + ((OResettable) o).reset(); + } else + size++; + } + return size; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("OMultiCollectionIterator.remove()"); + } + + public int getLimit() { + return limit; + } + + public void setLimit(final int limit) { + this.limit = limit; + } + + public int getSkip() { + return skip; + } + + public void setSkip(final int skip) { + this.skip = skip; + } + + public void setAutoConvertToRecord(final boolean autoConvert2Record) { + this.autoConvert2Record = autoConvert2Record; + } + + public boolean isAutoConvertToRecord() { + return autoConvert2Record; + } + + @Override + public boolean supportsFastContains() { + final int totSources = sources.size(); + for (int i = 0; i < totSources; ++i) { + final Object o = sources.get(i); + + if (o != null) { + if (o instanceof Set || o instanceof ORidBag) { + // OK + } else if (o instanceof OLazyWrapperIterator) { + if (!((OLazyWrapperIterator) o).canUseMultiValueDirectly()) + return false; + } else { + return false; + } + } + } + + return true; + } + + @Override + public boolean contains(final Object value) { + final int totSources = sources.size(); + for (int i = 0; i < totSources; ++i) { + Object o = sources.get(i); + + if (o != null) { + if (o instanceof OLazyWrapperIterator) + o = ((OLazyWrapperIterator) o).getMultiValue(); + + if (o instanceof Collection) { + if (((Collection) o).contains(value)) + return true; + } else if (o instanceof ORidBag) { + if (((ORidBag) o).contains((OIdentifiable) value)) + return true; + } + } + } + + return false; + } + + @SuppressWarnings("unchecked") + protected boolean getNextPartial() { + if (sourcesIterator != null) + while (sourcesIterator.hasNext()) { + Object next = sourcesIterator.next(); + if (next != null) { + + if (!(next instanceof ODocument) && next instanceof Iterable) + next = ((Iterable) next).iterator(); + + if (next instanceof OAutoConvertToRecord) + ((OAutoConvertToRecord) next).setAutoConvertToRecord(autoConvert2Record); + + if (next instanceof Iterator) { + if (next instanceof OResettable) + ((OResettable) next).reset(); + + if (((Iterator) next).hasNext()) { + partialIterator = (Iterator) next; + return true; + } + } else if (next instanceof Collection) { + if (!((Collection) next).isEmpty()) { + partialIterator = ((Collection) next).iterator(); + return true; + } + } else if (next.getClass().isArray()) { + final int arraySize = Array.getLength(next); + if (arraySize > 0) { + if (arraySize == 1) + partialIterator = new OIterableObject((T) Array.get(next, 0)); + else + partialIterator = (Iterator) OMultiValue.getMultiValueIterator(next, false); + return true; + } + } else { + partialIterator = new OIterableObject((T) next); + return true; + } + } + } + + return false; + } + + public boolean isEmbedded() { + return embedded; + } + + public OMultiCollectionIterator setEmbedded(final boolean embedded) { + this.embedded = embedded; + return this; + } + + @Override + public String toString() { + return "[" + size() + "]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OMultiValue.java b/core/src/main/java/com/orientechnologies/common/collection/OMultiValue.java new file mode 100755 index 00000000000..a0037457ead --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OMultiValue.java @@ -0,0 +1,844 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.Map.Entry; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.record.impl.ODocument; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Handles Multi-value types such as Arrays, Collections and Maps. It recognizes special Orient collections. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OMultiValue { + + /** + * Checks if a class is a multi-value type. + * + * @param iType + * Class to check + * @return true if it's an array, a collection or a map, otherwise false + */ + public static boolean isMultiValue(final Class iType) { + return OCollection.class.isAssignableFrom(iType) || Collection.class.isAssignableFrom(iType) || iType.isArray() + || Map.class.isAssignableFrom(iType) || OMultiCollectionIterator.class.isAssignableFrom(iType); + } + + /** + * Checks if the object is a multi-value type. + * + * @param iObject + * Object to check + * @return true if it's an array, a collection or a map, otherwise false + */ + public static boolean isMultiValue(final Object iObject) { + return iObject == null ? false : isMultiValue(iObject.getClass()); + } + + public static boolean isIterable(final Object iObject) { + return iObject == null ? false : iObject instanceof Iterable ? true : iObject instanceof Iterator; + } + + /** + * Returns the size of the multi-value object + * + * @param iObject + * Multi-value object (array, collection or map) + * @return the size of the multi value object + */ + public static int getSize(final Object iObject) { + if (iObject == null) + return 0; + + if (iObject instanceof OSizeable) + return ((OSizeable) iObject).size(); + + if (!isMultiValue(iObject)) + return 0; + + if (iObject instanceof Collection) + return ((Collection) iObject).size(); + if (iObject instanceof Map) + return ((Map) iObject).size(); + if (iObject.getClass().isArray()) + return Array.getLength(iObject); + return 0; + } + + /** + * Returns the first item of the Multi-value object (array, collection or map) + * + * @param iObject + * Multi-value object (array, collection or map) + * @return The first item if any + */ + public static Object getFirstValue(final Object iObject) { + if (iObject == null) + return null; + + if (!isMultiValue(iObject) || getSize(iObject) == 0) + return null; + + try { + if (iObject instanceof List) + return ((List) iObject).get(0); + else if (iObject instanceof Iterable) + return ((Iterable) iObject).iterator().next(); + else if (iObject instanceof Map) + return ((Map) iObject).values().iterator().next(); + else if (iObject.getClass().isArray()) + return Array.get(iObject, 0); + } catch (RuntimeException e) { + // IGNORE IT + OLogManager.instance().debug(iObject, "Error on reading the first item of the Multi-value field '%s'", iObject); + } + + return null; + } + + /** + * Returns the last item of the Multi-value object (array, collection or map) + * + * @param iObject + * Multi-value object (array, collection or map) + * @return The last item if any + */ + public static Object getLastValue(final Object iObject) { + if (iObject == null) + return null; + + if (!isMultiValue(iObject)) + return null; + + try { + if (iObject instanceof List) + return ((List) iObject).get(((List) iObject).size() - 1); + else if (iObject instanceof Iterable) { + Object last = null; + for (Object o : (Iterable) iObject) + last = o; + return last; + } else if (iObject instanceof Map) { + Object last = null; + for (Object o : ((Map) iObject).values()) + last = o; + return last; + } else if (iObject.getClass().isArray()) + return Array.get(iObject, Array.getLength(iObject) - 1); + } catch (RuntimeException e) { + // IGNORE IT + OLogManager.instance().debug(iObject, "Error on reading the last item of the Multi-value field '%s'", iObject); + } + + return null; + } + + /** + * Returns the iIndex item of the Multi-value object (array, collection or map) + * + * @param iObject + * Multi-value object (array, collection or map) + * @param iIndex + * integer as the position requested + * @return The first item if any + */ + public static Object getValue(final Object iObject, final int iIndex) { + if (iObject == null) + return null; + + if (!isMultiValue(iObject)) + return null; + + if (iIndex > getSize(iObject)) + return null; + + try { + if (iObject instanceof List) + return ((List) iObject).get(iIndex); + else if (iObject instanceof Set) { + int i = 0; + for (Object o : ((Set) iObject)) { + if (i++ == iIndex) { + return o; + } + } + } else if (iObject instanceof Map) { + int i = 0; + for (Object o : ((Map) iObject).values()) { + if (i++ == iIndex) { + return o; + } + } + } else if (iObject.getClass().isArray()) + return Array.get(iObject, iIndex); + else if (iObject instanceof Iterator || iObject instanceof Iterable) { + + final Iterator it = (iObject instanceof Iterable) ? ((Iterable) iObject).iterator() + : (Iterator) iObject; + for (int i = 0; it.hasNext(); ++i) { + final Object o = it.next(); + if (i == iIndex) { + if (it instanceof OResettable) + ((OResettable) it).reset(); + + return o; + } + } + + if (it instanceof OResettable) + ((OResettable) it).reset(); + } + } catch (RuntimeException e) { + // IGNORE IT + OLogManager.instance().debug(iObject, "Error on reading the first item of the Multi-value field '%s'", iObject); + } + return null; + } + + /** + * Sets the value of the Multi-value object (array or collection) at iIndex + * + * @param iObject + * Multi-value object (array, collection) + * @param iValue + * The value to set at this specified index. + * @param iIndex + * integer as the position requested + */ + public static void setValue(final Object iObject, final Object iValue, final int iIndex) { + if (iObject instanceof List) { + ((List) iObject).set(iIndex, iValue); + } else if (iObject.getClass().isArray()) { + Array.set(iObject, iIndex, iValue); + } else { + throw new IllegalArgumentException("Can only set positional indices for Lists and Arrays"); + } + } + + /** + * Returns an Iterable object to browse the multi-value instance (array, collection or map). + * + * @param iObject + * Multi-value object (array, collection or map) + */ + public static Iterable getMultiValueIterable(final Object iObject) { + if (iObject == null) + return null; + + if (iObject instanceof Iterable && !(iObject instanceof ODocument)) + return (Iterable) iObject; + else if (iObject instanceof Collection) + return ((Collection) iObject); + else if (iObject instanceof Map) + return ((Map) iObject).values(); + else if (iObject.getClass().isArray()) + return new OIterableObjectArray(iObject); + else if (iObject instanceof Iterator) { + final List temp = new ArrayList(); + for (Iterator it = (Iterator) iObject; it.hasNext();) + temp.add(it.next()); + return temp; + } + + return new OIterableObject(iObject); + } + + /** + * Returns an Iterable object to browse the multi-value instance (array, collection or map). + * + * @param iObject + * Multi-value object (array, collection or map) + * @param iForceConvertRecord + * allow to force settings to convert RIDs to records while browsing. + */ + public static Iterable getMultiValueIterable(final Object iObject, final boolean iForceConvertRecord) { + if (iObject == null) + return null; + + if (!iForceConvertRecord && iObject instanceof ORecordLazyMultiValue + && ((ORecordLazyMultiValue) iObject).isAutoConvertToRecord() != iForceConvertRecord) { + // RETURN THE LOW LEVEL ITERATOR + return new Iterable() { + @Override + public Iterator iterator() { + return ((ORecordLazyMultiValue) iObject).rawIterator(); + } + }; + } + + if (iObject instanceof Iterable && !(iObject instanceof ODocument)) + return (Iterable) iObject; + else if (iObject instanceof Collection) + return ((Collection) iObject); + else if (iObject instanceof Map) + return ((Map) iObject).values(); + else if (iObject.getClass().isArray()) + return new OIterableObjectArray(iObject); + else if (iObject instanceof Iterator) { + final List temp = new ArrayList(); + for (Iterator it = (Iterator) iObject; it.hasNext();) + temp.add(it.next()); + return temp; + } + + return new OIterableObject(iObject); + } + + /** + * Returns an Iterator object to browse the multi-value instance (array, collection or map) + * + * @param iObject + * Multi-value object (array, collection or map) + * @param iForceConvertRecord + * allow to force settings to convert RIDs to records while browsing. + */ + + public static Iterator getMultiValueIterator(final Object iObject, final boolean iForceConvertRecord) { + if (iObject == null) + return null; + + if (!iForceConvertRecord && iObject instanceof ORecordLazyMultiValue + && ((ORecordLazyMultiValue) iObject).isAutoConvertToRecord() != iForceConvertRecord) + // RETURN THE LOW LEVEL ITERATOR + return (Iterator) ((ORecordLazyMultiValue) iObject).rawIterator(); + + if (iObject instanceof Iterator) + return (Iterator) iObject; + + if (iObject instanceof Iterable) + return ((Iterable) iObject).iterator(); + if (iObject instanceof Map) + return ((Map) iObject).values().iterator(); + if (iObject.getClass().isArray()) + return new OIterableObjectArray(iObject).iterator(); + + return new OIterableObject(iObject); + } + + /** + * Returns an Iterator object to browse the multi-value instance (array, collection or map) + * + * @param iObject + * Multi-value object (array, collection or map) + */ + + public static Iterator getMultiValueIterator(final Object iObject) { + if (iObject == null) + return null; + + if (iObject instanceof Iterator) + return (Iterator) iObject; + + if (iObject instanceof Iterable) + return ((Iterable) iObject).iterator(); + if (iObject instanceof Map) + return ((Map) iObject).values().iterator(); + if (iObject.getClass().isArray()) + return new OIterableObjectArray(iObject).iterator(); + + return new OIterableObject(iObject); + } + + /** + * Returns a stringified version of the multi-value object. + * + * @param iObject + * Multi-value object (array, collection or map) + * @return a stringified version of the multi-value object. + */ + public static String toString(final Object iObject) { + final StringBuilder sb = new StringBuilder(2048); + + if (iObject instanceof Iterable) { + final Iterable coll = (Iterable) iObject; + + sb.append('['); + for (final Iterator it = coll.iterator(); it.hasNext();) { + try { + Object e = it.next(); + sb.append(e == iObject ? "(this Collection)" : e); + if (it.hasNext()) + sb.append(", "); + } catch (NoSuchElementException ex) { + // IGNORE THIS + } + } + return sb.append(']').toString(); + } else if (iObject instanceof Map) { + final Map map = (Map) iObject; + + Entry e; + + sb.append('{'); + for (final Iterator> it = map.entrySet().iterator(); it.hasNext();) { + try { + e = it.next(); + + sb.append(e.getKey()); + sb.append(":"); + sb.append(e.getValue() == iObject ? "(this Map)" : e.getValue()); + if (it.hasNext()) + sb.append(", "); + } catch (NoSuchElementException ex) { + // IGNORE THIS + } + } + return sb.append('}').toString(); + } + + return iObject.toString(); + } + + /** + * Utility function that add a value to the main object. It takes care about collections/array and single values. + * + * @param iObject + * MultiValue where to add value(s) + * @param iToAdd + * Single value, array of values or collections of values. Map are not supported. + * @return + */ + @SuppressFBWarnings("BC_UNCONFIRMED_CAST") + public static Object add(final Object iObject, final Object iToAdd) { + if (iObject != null) { + if (iObject instanceof Collection || iObject instanceof OCollection) { + // COLLECTION - ? + final OCollection coll; + if (iObject instanceof Collection) { + final Collection collection = (Collection) iObject; + coll = new OCollection() { + @Override + public void add(Object value) { + collection.add(value); + } + + @Override + public void remove(Object value) { + collection.remove(value); + } + + @Override + public Iterator iterator() { + return collection.iterator(); + } + + @Override + public int size() { + return collection.size(); + } + }; + } else + coll = (OCollection) iObject; + + if (!(iToAdd instanceof Map) && isMultiValue(iToAdd)) { + // COLLECTION - COLLECTION + for (Object o : getMultiValueIterable(iToAdd, false)) { + if (!(o instanceof Map) && isMultiValue(o)) + add(coll, o); + else + coll.add(o); + } + } + + else if (iToAdd != null && iToAdd.getClass().isArray()) { + // ARRAY - COLLECTION + for (int i = 0; i < Array.getLength(iToAdd); ++i) { + Object o = Array.get(iToAdd, i); + if (!(o instanceof Map) && isMultiValue(o)) + add(coll, o); + else + coll.add(o); + } + + } else if (iToAdd instanceof Map) { + // MAP + for (Entry entry : ((Map) iToAdd).entrySet()) + coll.add(entry.getValue()); + } else if (iToAdd instanceof Iterator) { + // ITERATOR + for (Iterator it = (Iterator) iToAdd; it.hasNext();) + coll.add(it.next()); + } else + coll.add(iToAdd); + + } else if (iObject.getClass().isArray()) { + // ARRAY - ? + + final Object[] copy; + if (iToAdd instanceof Collection) { + // ARRAY - COLLECTION + final int tot = Array.getLength(iObject) + ((Collection) iToAdd).size(); + copy = Arrays.copyOf((Object[]) iObject, tot); + final Iterator it = ((Collection) iToAdd).iterator(); + for (int i = Array.getLength(iObject); i < tot; ++i) + copy[i] = it.next(); + + } else if (iToAdd != null && iToAdd.getClass().isArray()) { + // ARRAY - ARRAY + final int tot = Array.getLength(iObject) + Array.getLength(iToAdd); + copy = Arrays.copyOf((Object[]) iObject, tot); + System.arraycopy(iToAdd, 0, iObject, Array.getLength(iObject), Array.getLength(iToAdd)); + + } else { + copy = Arrays.copyOf((Object[]) iObject, Array.getLength(iObject) + 1); + copy[copy.length - 1] = iToAdd; + } + return copy; + } + } + + return iObject; + } + + /** + * Utility function that remove a value from the main object. It takes care about collections/array and single values. + * + * @param iObject + * MultiValue where to add value(s) + * @param iToRemove + * Single value, array of values or collections of values. Map are not supported. + * @param iAllOccurrences + * True if the all occurrences must be removed or false of only the first one (Like java.util.Collection.remove()) + * @return + */ + public static Object remove(Object iObject, Object iToRemove, final boolean iAllOccurrences) { + if (iObject != null) { + if (iObject instanceof OMultiCollectionIterator) { + final Collection list = new LinkedList(); + for (Object o : ((OMultiCollectionIterator) iObject)) + list.add(o); + iObject = list; + } + + if (iToRemove instanceof OMultiCollectionIterator) { + // TRANSFORM IN SET ONCE TO OPTIMIZE LOOPS DURING REMOVE + final Set set = new HashSet(); + for (Object o : ((OMultiCollectionIterator) iToRemove)) + set.add(o); + iToRemove = set; + } + + if (iObject instanceof Collection || iObject instanceof OCollection) { + // COLLECTION - ? + + final OCollection coll; + if (iObject instanceof Collection) { + final Collection collection = (Collection) iObject; + coll = new OCollection() { + @Override + public void add(Object value) { + collection.add(value); + } + + @Override + public void remove(Object value) { + collection.remove(value); + } + + @Override + public Iterator iterator() { + return collection.iterator(); + } + + @Override + public int size() { + return collection.size(); + } + }; + } else + coll = (OCollection) iObject; + + if (iToRemove instanceof Collection) { + // COLLECTION - COLLECTION + for (Object o : (Collection) iToRemove) { + if (isMultiValue(o)) + remove(coll, o, iAllOccurrences); + else + removeFromOCollection(iObject, coll, o, iAllOccurrences); + } + } + + else if (iToRemove != null && iToRemove.getClass().isArray()) { + // ARRAY - COLLECTION + for (int i = 0; i < Array.getLength(iToRemove); ++i) { + Object o = Array.get(iToRemove, i); + if (isMultiValue(o)) + remove(coll, o, iAllOccurrences); + else + removeFromOCollection(iObject, coll, o, iAllOccurrences); + } + + } else if (iToRemove instanceof Map) { + // MAP + for (Entry entry : ((Map) iToRemove).entrySet()) + coll.remove(entry.getKey()); + } else if (iToRemove instanceof Iterator) { + // ITERATOR + if (iToRemove instanceof OMultiCollectionIterator) + ((OMultiCollectionIterator) iToRemove).reset(); + + if (iAllOccurrences) { + if (iObject instanceof OCollection) + throw new IllegalStateException("Mutable collection cannot be used to remove all occurrences."); + + final Collection collection = (Collection) iObject; + OMultiCollectionIterator it = (OMultiCollectionIterator) iToRemove; + batchRemove(collection, it); + } else { + Iterator it = (Iterator) iToRemove; + if (it.hasNext()) { + final Object itemToRemove = it.next(); + coll.remove(itemToRemove); + } + } + } else + removeFromOCollection(iObject, coll, iToRemove, iAllOccurrences); + + } else if (iObject.getClass().isArray()) { + // ARRAY - ? + + final Object[] copy; + if (iToRemove instanceof Collection) { + // ARRAY - COLLECTION + final int sourceTot = Array.getLength(iObject); + final int tot = sourceTot - ((Collection) iToRemove).size(); + copy = new Object[tot]; + + int k = 0; + for (int i = 0; i < sourceTot; ++i) { + Object o = Array.get(iObject, i); + if (o != null) { + boolean found = false; + for (Object toRemove : (Collection) iToRemove) { + if (o.equals(toRemove)) { + // SKIP + found = true; + break; + } + } + + if (!found) + copy[k++] = o; + } + } + + } else if (iToRemove != null && iToRemove.getClass().isArray()) { + throw new UnsupportedOperationException("Cannot execute remove() against an array"); + + } else { + throw new UnsupportedOperationException("Cannot execute remove() against an array"); + } + return copy; + + } else + throw new IllegalArgumentException("Object " + iObject + " is not a multi value"); + } + + return iObject; + } + + protected static void removeFromOCollection(final Object iObject, final OCollection coll, final Object iToRemove, + final boolean iAllOccurrences) { + if (iAllOccurrences && !(iObject instanceof Set)) { + // BROWSE THE COLLECTION ONE BY ONE TO REMOVE ALL THE OCCURRENCES + final Iterator it = coll.iterator(); + while (it.hasNext()) { + final Object o = it.next(); + if (iToRemove.equals(o)) + it.remove(); + } + } else + coll.remove(iToRemove); + + } + + private static void batchRemove(Collection coll, Iterator it) { + int approximateRemainingSize; + if (it instanceof OSizeable) { + approximateRemainingSize = ((OSizeable) it).size(); + } else { + approximateRemainingSize = -1; + } + + while (it.hasNext()) { + Set batch = prepareBatch(it, approximateRemainingSize); + coll.removeAll(batch); + approximateRemainingSize -= batch.size(); + } + } + + private static Set prepareBatch(Iterator it, int approximateRemainingSize) { + final HashSet batch; + if (approximateRemainingSize > -1) { + if (approximateRemainingSize > 10000) + batch = new HashSet(13400); + else + batch = new HashSet((int) (approximateRemainingSize / 0.75)); + } else { + batch = new HashSet(); + } + + int count = 0; + while (count < 10000 && it.hasNext()) { + batch.add(it.next()); + count++; + } + + return batch; + } + + public static Object[] array(final Object iValue) { + return array(iValue, Object.class); + } + + public static T[] array(final Object iValue, final Class iClass) { + return array(iValue, iClass, null); + } + + @SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS") + public static T[] array(final Object iValue, final Class iClass, final OCallable iCallback) { + if (iValue == null) + return null; + + final T[] result; + + if (isMultiValue(iValue)) { + // CREATE STATIC ARRAY AND FILL IT + result = (T[]) Array.newInstance(iClass, getSize(iValue)); + int i = 0; + for (Iterator it = (Iterator) getMultiValueIterator(iValue, false); it.hasNext(); ++i) + result[i] = (T) convert(it.next(), iCallback); + } else if (isIterable(iValue)) { + // SIZE UNKNOWN: USE A LIST AS TEMPORARY OBJECT + final List temp = new ArrayList(); + for (Iterator it = (Iterator) getMultiValueIterator(iValue, false); it.hasNext();) + temp.add((T) convert(it.next(), iCallback)); + + if (iClass.equals(Object.class)) + result = (T[]) temp.toArray(); + else + // CONVERT THEM + result = temp.toArray((T[]) Array.newInstance(iClass, getSize(iValue))); + + } else { + result = (T[]) Array.newInstance(iClass, 1); + result[0] = (T) (T) convert(iValue, iCallback); + } + + return result; + } + + public static Object convert(final Object iObject, final OCallable iCallback) { + return iCallback != null ? iCallback.call(iObject) : iObject; + } + + public static boolean equals(final Collection col1, final Collection col2) { + if (col1.size() != col2.size()) + return false; + return col1.containsAll(col2) && col2.containsAll(col1); + } + + public static boolean contains(final Object iObject, final Object iItem) { + if (iObject == null) + return false; + + if (iObject instanceof Collection) + return ((Collection) iObject).contains(iItem); + + else if (iObject.getClass().isArray()) { + final int size = Array.getLength(iObject); + for (int i = 0; i < size; ++i) { + final Object item = Array.get(iObject, i); + if (item != null && item.equals(iItem)) + return true; + } + } + + return false; + } + + public static int indexOf(final Object iObject, final Object iItem) { + if (iObject == null) + return -1; + + if (iObject instanceof List) + return ((List) iObject).indexOf(iItem); + + else if (iObject.getClass().isArray()) { + final int size = Array.getLength(iObject); + for (int i = 0; i < size; ++i) { + final Object item = Array.get(iObject, i); + if (item != null && item.equals(iItem)) + return i; + } + } + + return -1; + } + + public static Object toSet(final Object o) { + if (o instanceof Set) + return o; + else if (o instanceof Collection) + return new HashSet((Collection) o); + else if (o instanceof Map) { + final Collection values = ((Map) o).values(); + return values instanceof Set ? values : new HashSet(values); + } else if (o.getClass().isArray()) { + final HashSet set = new HashSet(); + int tot = Array.getLength(o); + for (int i = 0; i < tot; ++i) { + set.add(Array.get(o, i)); + } + return set; + } else if (o instanceof OMultiValue) { + } else if (o instanceof Iterator) { + final HashSet set = new HashSet(); + while (((Iterator) o).hasNext()) { + set.add(((Iterator) o).next()); + } + + if (o instanceof OResettable) + ((OResettable) o).reset(); + + return set; + } + + final HashSet set = new HashSet(1); + set.add(o); + return set; + } + + public static List getSingletonList(final T item){ + final List list = new ArrayList(1); + list.add(item); + return list; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/ONavigableMap.java b/core/src/main/java/com/orientechnologies/common/collection/ONavigableMap.java new file mode 100644 index 00000000000..174158cba3a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/ONavigableMap.java @@ -0,0 +1,330 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; + +/** + * This interface emulates the NavigableMap of Java 1.6. + */ +public interface ONavigableMap extends SortedMap { + /** + * Returns a key-value mapping associated with the greatest key strictly less than the given key, or {@code null} if there is no + * such key. + * + * @param key + * the key + * @return an entry with the greatest key less than {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + Map.Entry lowerEntry(K key); + + /** + * Returns the greatest key strictly less than the given key, or {@code null} if there is no such key. + * + * @param key + * the key + * @return the greatest key less than {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + K lowerKey(K key); + + /** + * Returns a key-value mapping associated with the greatest key less than or equal to the given key, or {@code null} if there is + * no such key. + * + * @param key + * the key + * @return an entry with the greatest key less than or equal to {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + Map.Entry floorEntry(K key); + + /** + * Returns the greatest key less than or equal to the given key, or {@code null} if there is no such key. + * + * @param key + * the key + * @return the greatest key less than or equal to {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + K floorKey(K key); + + /** + * Returns a key-value mapping associated with the least key greater than or equal to the given key, or {@code null} if there is + * no such key. + * + * @param key + * the key + * @return an entry with the least key greater than or equal to {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + Map.Entry ceilingEntry(K key); + + /** + * Returns the least key greater than or equal to the given key, or {@code null} if there is no such key. + * + * @param key + * the key + * @return the least key greater than or equal to {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + K ceilingKey(K key); + + /** + * Returns a key-value mapping associated with the least key strictly greater than the given key, or {@code null} if there is no + * such key. + * + * @param key + * the key + * @return an entry with the least key greater than {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + Map.Entry higherEntry(K key); + + /** + * Returns the least key strictly greater than the given key, or {@code null} if there is no such key. + * + * @param key + * the key + * @return the least key greater than {@code key}, or {@code null} if there is no such key + * @throws ClassCastException + * if the specified key cannot be compared with the keys currently in the map + * @throws NullPointerException + * if the specified key is null and this map does not permit null keys + */ + K higherKey(K key); + + /** + * Returns a key-value mapping associated with the least key in this map, or {@code null} if the map is empty. + * + * @return an entry with the least key, or {@code null} if this map is empty + */ + Map.Entry firstEntry(); + + /** + * Returns a key-value mapping associated with the greatest key in this map, or {@code null} if the map is empty. + * + * @return an entry with the greatest key, or {@code null} if this map is empty + */ + Map.Entry lastEntry(); + + /** + * Removes and returns a key-value mapping associated with the least key in this map, or {@code null} if the map is empty. + * + * @return the removed first entry of this map, or {@code null} if this map is empty + */ + Map.Entry pollFirstEntry(); + + /** + * Removes and returns a key-value mapping associated with the greatest key in this map, or {@code null} if the map is empty. + * + * @return the removed last entry of this map, or {@code null} if this map is empty + */ + Map.Entry pollLastEntry(); + + /** + * Returns a reverse order view of the mappings contained in this map. The descending map is backed by this map, so changes to the + * map are reflected in the descending map, and vice-versa. If either map is modified while an iteration over a collection view of + * either map is in progress (except through the iterator's own {@code remove} operation), the results of the iteration are + * undefined. + * + *

+ * The returned map has an ordering equivalent to + * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). The expression + * {@code m.descendingMap().descendingMap()} returns a view of {@code m} essentially equivalent to {@code m}. + * + * @return a reverse order view of this map + */ + ONavigableMap descendingMap(); + + /** + * Returns a {@link ONavigableSet} view of the keys contained in this map. The set's iterator returns the keys in ascending order. + * The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an + * iteration over the set is in progress (except through the iterator's own {@code remove} operation), the results of the + * iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, {@code retainAll}, and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + * + * @return a navigable set view of the keys in this map + */ + ONavigableSet navigableKeySet(); + + /** + * Returns a reverse order {@link ONavigableSet} view of the keys contained in this map. The set's iterator returns the keys in + * descending order. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is + * modified while an iteration over the set is in progress (except through the iterator's own {@code remove} operation), the + * results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, {@code retainAll}, and {@code clear} operations. It + * does not support the {@code add} or {@code addAll} operations. + * + * @return a reverse order navigable set view of the keys in this map + */ + ONavigableSet descendingKeySet(); + + /** + * Returns a view of the portion of this map whose keys range from {@code fromKey} to {@code toKey}. If {@code fromKey} and + * {@code toKey} are equal, the returned map is empty unless {@code fromExclusive} and {@code toExclusive} are both true. The + * returned map is backed by this map, so changes in the returned map are reflected in this map, and vice-versa. The returned map + * supports all optional map operations that this map supports. + * + *

+ * The returned map will throw an {@code IllegalArgumentException} on an attempt to insert a key outside of its range, or to + * construct a submap either of whose endpoints lie outside its range. + * + * @param fromKey + * low endpoint of the keys in the returned map + * @param fromInclusive + * {@code true} if the low endpoint is to be included in the returned view + * @param toKey + * high endpoint of the keys in the returned map + * @param toInclusive + * {@code true} if the high endpoint is to be included in the returned view + * @return a view of the portion of this map whose keys range from {@code fromKey} to {@code toKey} + * @throws ClassCastException + * if {@code fromKey} and {@code toKey} cannot be compared to one another using this map's comparator (or, if the map + * has no comparator, using natural ordering). Implementations may, but are not required to, throw this exception if + * {@code fromKey} or {@code toKey} cannot be compared to keys currently in the map. + * @throws NullPointerException + * if {@code fromKey} or {@code toKey} is null and this map does not permit null keys + * @throws IllegalArgumentException + * if {@code fromKey} is greater than {@code toKey}; or if this map itself has a restricted range, and {@code fromKey} + * or {@code toKey} lies outside the bounds of the range + */ + ONavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive); + + /** + * Returns a view of the portion of this map whose keys are less than (or equal to, if {@code inclusive} is true) {@code toKey}. + * The returned map is backed by this map, so changes in the returned map are reflected in this map, and vice-versa. The returned + * map supports all optional map operations that this map supports. + * + *

+ * The returned map will throw an {@code IllegalArgumentException} on an attempt to insert a key outside its range. + * + * @param toKey + * high endpoint of the keys in the returned map + * @param inclusive + * {@code true} if the high endpoint is to be included in the returned view + * @return a view of the portion of this map whose keys are less than (or equal to, if {@code inclusive} is true) {@code toKey} + * @throws ClassCastException + * if {@code toKey} is not compatible with this map's comparator (or, if the map has no comparator, if {@code toKey} + * does not implement {@link Comparable}). Implementations may, but are not required to, throw this exception if + * {@code toKey} cannot be compared to keys currently in the map. + * @throws NullPointerException + * if {@code toKey} is null and this map does not permit null keys + * @throws IllegalArgumentException + * if this map itself has a restricted range, and {@code toKey} lies outside the bounds of the range + */ + ONavigableMap headMap(K toKey, boolean inclusive); + + /** + * Returns a view of the portion of this map whose keys are greater than (or equal to, if {@code inclusive} is true) + * {@code fromKey}. The returned map is backed by this map, so changes in the returned map are reflected in this map, and + * vice-versa. The returned map supports all optional map operations that this map supports. + * + *

+ * The returned map will throw an {@code IllegalArgumentException} on an attempt to insert a key outside its range. + * + * @param fromKey + * low endpoint of the keys in the returned map + * @param inclusive + * {@code true} if the low endpoint is to be included in the returned view + * @return a view of the portion of this map whose keys are greater than (or equal to, if {@code inclusive} is true) + * {@code fromKey} + * @throws ClassCastException + * if {@code fromKey} is not compatible with this map's comparator (or, if the map has no comparator, if {@code fromKey} + * does not implement {@link Comparable}). Implementations may, but are not required to, throw this exception if + * {@code fromKey} cannot be compared to keys currently in the map. + * @throws NullPointerException + * if {@code fromKey} is null and this map does not permit null keys + * @throws IllegalArgumentException + * if this map itself has a restricted range, and {@code fromKey} lies outside the bounds of the range + */ + ONavigableMap tailMap(K fromKey, boolean inclusive); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code subMap(fromKey, true, toKey, false)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + SortedMap subMap(K fromKey, K toKey); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code headMap(toKey, false)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + SortedMap headMap(K toKey); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code tailMap(fromKey, true)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + SortedMap tailMap(K fromKey); +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/ONavigableSet.java b/core/src/main/java/com/orientechnologies/common/collection/ONavigableSet.java new file mode 100644 index 00000000000..c3ffae8376a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/ONavigableSet.java @@ -0,0 +1,250 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.SortedSet; + +/** + * This interface emulates the NavigableSet of Java 1.6. + */ +public interface ONavigableSet extends SortedSet { + /** + * Returns the greatest element in this set strictly less than the given element, or {@code null} if there is no such element. + * + * @param e + * the value to match + * @return the greatest element less than {@code e}, or {@code null} if there is no such element + * @throws ClassCastException + * if the specified element cannot be compared with the elements currently in the set + * @throws NullPointerException + * if the specified element is null and this set does not permit null elements + */ + E lower(E e); + + /** + * Returns the greatest element in this set less than or equal to the given element, or {@code null} if there is no such element. + * + * @param e + * the value to match + * @return the greatest element less than or equal to {@code e}, or {@code null} if there is no such element + * @throws ClassCastException + * if the specified element cannot be compared with the elements currently in the set + * @throws NullPointerException + * if the specified element is null and this set does not permit null elements + */ + E floor(E e); + + /** + * Returns the least element in this set greater than or equal to the given element, or {@code null} if there is no such element. + * + * @param e + * the value to match + * @return the least element greater than or equal to {@code e}, or {@code null} if there is no such element + * @throws ClassCastException + * if the specified element cannot be compared with the elements currently in the set + * @throws NullPointerException + * if the specified element is null and this set does not permit null elements + */ + E ceiling(E e); + + /** + * Returns the least element in this set strictly greater than the given element, or {@code null} if there is no such element. + * + * @param e + * the value to match + * @return the least element greater than {@code e}, or {@code null} if there is no such element + * @throws ClassCastException + * if the specified element cannot be compared with the elements currently in the set + * @throws NullPointerException + * if the specified element is null and this set does not permit null elements + */ + E higher(E e); + + /** + * Retrieves and removes the first (lowest) element, or returns {@code null} if this set is empty. + * + * @return the first element, or {@code null} if this set is empty + */ + E pollFirst(); + + /** + * Retrieves and removes the last (highest) element, or returns {@code null} if this set is empty. + * + * @return the last element, or {@code null} if this set is empty + */ + E pollLast(); + + /** + * Returns an iterator over the elements in this set, in ascending order. + * + * @return an iterator over the elements in this set, in ascending order + */ + OLazyIterator iterator(); + + /** + * Returns a reverse order view of the elements contained in this set. The descending set is backed by this set, so changes to the + * set are reflected in the descending set, and vice-versa. If either set is modified while an iteration over either set is in + * progress (except through the iterator's own {@code remove} operation), the results of the iteration are undefined. + * + *

+ * The returned set has an ordering equivalent to + * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). The expression + * {@code s.descendingSet().descendingSet()} returns a view of {@code s} essentially equivalent to {@code s}. + * + * @return a reverse order view of this set + */ + ONavigableSet descendingSet(); + + /** + * Returns an iterator over the elements in this set, in descending order. Equivalent in effect to + * {@code descendingSet().iterator()}. + * + * @return an iterator over the elements in this set, in descending order + */ + Iterator descendingIterator(); + + /** + * Returns a view of the portion of this set whose elements range from {@code fromElement} to {@code toElement}. If + * {@code fromElement} and {@code toElement} are equal, the returned set is empty unless {@code fromExclusive} and + * {@code toExclusive} are both true. The returned set is backed by this set, so changes in the returned set are reflected in this + * set, and vice-versa. The returned set supports all optional set operations that this set supports. + * + *

+ * The returned set will throw an {@code IllegalArgumentException} on an attempt to insert an element outside its range. + * + * @param fromElement + * low endpoint of the returned set + * @param fromInclusive + * {@code true} if the low endpoint is to be included in the returned view + * @param toElement + * high endpoint of the returned set + * @param toInclusive + * {@code true} if the high endpoint is to be included in the returned view + * @return a view of the portion of this set whose elements range from {@code fromElement}, inclusive, to {@code toElement}, + * exclusive + * @throws ClassCastException + * if {@code fromElement} and {@code toElement} cannot be compared to one another using this set's comparator (or, if + * the set has no comparator, using natural ordering). Implementations may, but are not required to, throw this + * exception if {@code fromElement} or {@code toElement} cannot be compared to elements currently in the set. + * @throws NullPointerException + * if {@code fromElement} or {@code toElement} is null and this set does not permit null elements + * @throws IllegalArgumentException + * if {@code fromElement} is greater than {@code toElement}; or if this set itself has a restricted range, and + * {@code fromElement} or {@code toElement} lies outside the bounds of the range. + */ + ONavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); + + /** + * Returns a view of the portion of this set whose elements are less than (or equal to, if {@code inclusive} is true) + * {@code toElement}. The returned set is backed by this set, so changes in the returned set are reflected in this set, and + * vice-versa. The returned set supports all optional set operations that this set supports. + * + *

+ * The returned set will throw an {@code IllegalArgumentException} on an attempt to insert an element outside its range. + * + * @param toElement + * high endpoint of the returned set + * @param inclusive + * {@code true} if the high endpoint is to be included in the returned view + * @return a view of the portion of this set whose elements are less than (or equal to, if {@code inclusive} is true) + * {@code toElement} + * @throws ClassCastException + * if {@code toElement} is not compatible with this set's comparator (or, if the set has no comparator, if + * {@code toElement} does not implement {@link Comparable}). Implementations may, but are not required to, throw this + * exception if {@code toElement} cannot be compared to elements currently in the set. + * @throws NullPointerException + * if {@code toElement} is null and this set does not permit null elements + * @throws IllegalArgumentException + * if this set itself has a restricted range, and {@code toElement} lies outside the bounds of the range + */ + ONavigableSet headSet(E toElement, boolean inclusive); + + /** + * Returns a view of the portion of this set whose elements are greater than (or equal to, if {@code inclusive} is true) + * {@code fromElement}. The returned set is backed by this set, so changes in the returned set are reflected in this set, and + * vice-versa. The returned set supports all optional set operations that this set supports. + * + *

+ * The returned set will throw an {@code IllegalArgumentException} on an attempt to insert an element outside its range. + * + * @param fromElement + * low endpoint of the returned set + * @param inclusive + * {@code true} if the low endpoint is to be included in the returned view + * @return a view of the portion of this set whose elements are greater than or equal to {@code fromElement} + * @throws ClassCastException + * if {@code fromElement} is not compatible with this set's comparator (or, if the set has no comparator, if + * {@code fromElement} does not implement {@link Comparable}). Implementations may, but are not required to, throw this + * exception if {@code fromElement} cannot be compared to elements currently in the set. + * @throws NullPointerException + * if {@code fromElement} is null and this set does not permit null elements + * @throws IllegalArgumentException + * if this set itself has a restricted range, and {@code fromElement} lies outside the bounds of the range + */ + ONavigableSet tailSet(E fromElement, boolean inclusive); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code subSet(fromElement, true, toElement, false)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + SortedSet subSet(E fromElement, E toElement); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code headSet(toElement, false)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} na + */ + SortedSet headSet(E toElement); + + /** + * {@inheritDoc} + * + *

+ * Equivalent to {@code tailSet(fromElement, true)}. + * + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + SortedSet tailSet(E fromElement); +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OSimpleImmutableEntry.java b/core/src/main/java/com/orientechnologies/common/collection/OSimpleImmutableEntry.java new file mode 100644 index 00000000000..a3b0aac24e1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OSimpleImmutableEntry.java @@ -0,0 +1,144 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import java.util.Map; +import java.util.Map.Entry; + +public class OSimpleImmutableEntry implements Entry, java.io.Serializable { + private static final long serialVersionUID = 7138329143949025153L; + + private final K key; + private final V value; + + /** + * Creates an entry representing a mapping from the specified key to the specified value. + * + * @param key + * the key represented by this entry + * @param value + * the value represented by this entry + */ + public OSimpleImmutableEntry(final K key, final V value) { + this.key = key; + this.value = value; + } + + /** + * Creates an entry representing the same mapping as the specified entry. + * + * @param entry + * the entry to copy + */ + public OSimpleImmutableEntry(final Entry entry) { + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + /** + * Returns the key corresponding to this entry. + * + * @return the key corresponding to this entry + */ + public K getKey() { + return key; + } + + /** + * Returns the value corresponding to this entry. + * + * @return the value corresponding to this entry + */ + public V getValue() { + return value; + } + + /** + * Replaces the value corresponding to this entry with the specified value (optional operation). This implementation simply throws + * UnsupportedOperationException, as this class implements an immutable map entry. + * + * @param value + * new value to be stored in this entry + * @return (Does not return) + * @throws UnsupportedOperationException + * always + */ + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + /** + * Compares the specified object with this entry for equality. Returns {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. More formally, two entries {@code e1} and {@code e2} represent the same mapping if + * + *

+	 * (e1.getKey() == null ? e2.getKey() == null : e1.getKey().equals(e2.getKey()))
+	 * 		&& (e1.getValue() == null ? e2.getValue() == null : e1.getValue().equals(e2.getValue()))
+	 * 
+ * + * This ensures that the {@code equals} method works properly across different implementations of the {@code Map.Entry} interface. + * + * @param o + * object to be compared for equality with this map entry + * @return {@code true} if the specified object is equal to this map entry + * @see #hashCode + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + private boolean eq(final Object o1, final Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * Returns the hash code value for this map entry. The hash code of a map entry {@code e} is defined to be: + * + *
+	 * (e.getKey() == null ? 0 : e.getKey().hashCode()) ˆ (e.getValue() == null ? 0 : e.getValue().hashCode())
+	 * 
+ * + * This ensures that {@code e1.equals(e2)} implies that {@code e1.hashCode()==e2.hashCode()} for any two Entries {@code e1} and + * {@code e2}, as required by the general contract of {@link Object#hashCode}. + * + * @return the hash code value for this map entry + * @see #equals + */ + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); + } + + /** + * Returns a String representation of this map entry. This implementation returns the string representation of this entry's key + * followed by the equals character ("=") followed by the string representation of this entry's value. + * + * @return a String representation of this map entry + */ + @Override + public String toString() { + return key + "=" + value; + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/OSortedMultiIterator.java b/core/src/main/java/com/orientechnologies/common/collection/OSortedMultiIterator.java new file mode 100644 index 00000000000..c2609186fee --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/OSortedMultiIterator.java @@ -0,0 +1,151 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.collection; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.parser.OOrderBy; +import com.orientechnologies.orient.core.sql.parser.OOrderByItem; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class OSortedMultiIterator implements Iterator { + + private static final int STATUS_INIT = 0; + private static final int STATUS_RUNNING = 1; + + private final OOrderBy orderBy; + + private List> sourceIterators = new ArrayList>(); + private List heads = new ArrayList(); + + private int status = STATUS_INIT; + + public OSortedMultiIterator(OOrderBy orderBy) { + this.orderBy = orderBy; + + } + + public void add(Iterator iterator) { + if (status == STATUS_INIT) { + sourceIterators.add(iterator); + if (iterator.hasNext()) { + heads.add(iterator.next()); + } else { + heads.add(null); + } + } else { + throw new IllegalStateException("You are trying to add a sub-iterator on a running OSortedMultiIterator"); + } + } + + @Override + public boolean hasNext() { + if (status == STATUS_INIT) { + status = STATUS_RUNNING; + } + for (T o : heads) { + if (o != null) { + return true; + } + } + return false; + } + + @Override + public T next() { + if (status == STATUS_INIT) { + status = STATUS_RUNNING; + } + int nextItemPosition = findNextPosition(); + T result = heads.get(nextItemPosition); + if (sourceIterators.get(nextItemPosition).hasNext()) { + heads.set(nextItemPosition, sourceIterators.get(nextItemPosition).next()); + } else { + heads.set(nextItemPosition, null); + } + return result; + } + + private int findNextPosition() { + int lastPosition = 0; + while (heads.size() < lastPosition && heads.get(lastPosition) == null) { + lastPosition++; + } + T lastItem = heads.get(lastPosition); + for (int i = lastPosition + 1; i < heads.size(); i++) { + T item = heads.get(i); + if (item == null) { + continue; + } + if (comesFrist(item, lastItem)) { + lastItem = item; + lastPosition = i; + } + } + return lastPosition; + } + + protected boolean comesFrist(T left, T right) { + if (orderBy == null || orderBy.getItems() == null || orderBy.getItems().size() == 0) { + return true; + } + if (right == null) { + return true; + } + if (left == null) { + return false; + } + + ODocument leftDoc = (left instanceof ODocument) ? (ODocument) left : (ODocument) left.getRecord(); + ODocument rightDoc = (right instanceof ODocument) ? (ODocument) right : (ODocument) right.getRecord(); + + for (OOrderByItem orderItem : orderBy.getItems()) { + Object leftVal = leftDoc.field(orderItem.getRecordAttr()); + Object rightVal = rightDoc.field(orderItem.getRecordAttr()); + if (rightVal == null) { + return true; + } + if (leftVal == null) { + return false; + } + if (leftVal instanceof Comparable) { + int compare = ((Comparable) leftVal).compareTo(rightVal); + if (compare == 0) { + continue; + } + boolean greater = compare > 0; + if (OOrderByItem.DESC.equals(orderItem.getType())) { + return greater; + } else { + return !greater; + } + } + } + + return false; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableEntry.java b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableEntry.java new file mode 100644 index 00000000000..d9cb6f2eb85 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableEntry.java @@ -0,0 +1,200 @@ +package com.orientechnologies.common.collection.closabledictionary; + +import javax.annotation.concurrent.GuardedBy; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Internal presentation of entry inside of {@link OClosableLinkedContainer} + * + * @param Key type. + * @param Value type. + */ +public class OClosableEntry { + /** + * Constant for open state of entries state machine. + */ + private static final long STATUS_OPEN = 1; + + /** + * Constant for closed state of entries state machine. + */ + private static final long STATUS_CLOSED = 2; + + /** + * Constant for retired state of entries state machine. + */ + private static final long STATUS_RETIRED = 4; + + /** + * Constant for dead state of entry state machine. + */ + private static final long STATUS_DEAD = 5; + + /** + * Because entry may be acquired by several threads acquired status instead of single constant is presented as number of + * acquires and is stored in 4 most significant bytes of {@link #state} field. + */ + private static final long ACQUIRED_OFFSET = 32; + + @GuardedBy("lruLock") + private OClosableEntry next; + + @GuardedBy("lruLock") + private OClosableEntry prev; + + @GuardedBy("lruLock") + public OClosableEntry getNext() { + return next; + } + + @GuardedBy("lruLock") + public void setNext(OClosableEntry next) { + this.next = next; + } + + @GuardedBy("lruLock") + public OClosableEntry getPrev() { + return prev; + } + + @GuardedBy("lruLock") + public void setPrev(OClosableEntry prev) { + this.prev = prev; + } + + private final V item; + + /** + * Current state of state machine + */ + @GuardedBy("stateLock") + private volatile long state = STATUS_OPEN; + private final Lock stateLock = new ReentrantLock(); + + OClosableEntry(V item) { + this.item = item; + } + + public V get() { + return item; + } + + void acquireStateLock() { + stateLock.lock(); + } + + void releaseStateLock() { + stateLock.unlock(); + } + + public static boolean isOpen(long state) { + return state == STATUS_OPEN; + } + + @GuardedBy("stateLock") + void makeAcquiredFromClosed(OClosableItem item) { + final long s = state; + if (s != STATUS_CLOSED) + throw new IllegalStateException(); + + final long acquiredState = 1L << ACQUIRED_OFFSET; + item.open(); + + state = acquiredState; + } + + @GuardedBy("stateLock") + void makeAcquiredFromOpen() { + if (state != STATUS_OPEN) + throw new IllegalStateException(); + + state = 1L << ACQUIRED_OFFSET; + } + + void releaseAcquired() { + stateLock.lock(); + try { + long acquireCount = state >>> ACQUIRED_OFFSET; + + if (acquireCount < 1) + throw new IllegalStateException("Amount of acquires less than one"); + + acquireCount--; + + if (acquireCount < 1) + state = STATUS_OPEN; + else + state = acquireCount << ACQUIRED_OFFSET; + } finally { + stateLock.unlock(); + } + } + + @GuardedBy("stateLock") + void incrementAcquired() { + long acquireCount = state >>> ACQUIRED_OFFSET; + + if (acquireCount < 1) + throw new IllegalStateException(); + + acquireCount++; + state = acquireCount << ACQUIRED_OFFSET; + } + + long makeRetired() { + long oldSate = state; + + stateLock.lock(); + try { + state = STATUS_RETIRED; + } finally { + stateLock.unlock(); + } + + return oldSate; + } + + void makeDead() { + stateLock.lock(); + try { + state = STATUS_DEAD; + } finally { + stateLock.unlock(); + } + } + + boolean makeClosed() { + stateLock.lock(); + try { + if (state == STATUS_CLOSED) + return true; + + if (state != STATUS_OPEN) + return false; + + item.close(); + state = STATUS_CLOSED; + } finally { + stateLock.unlock(); + } + + return true; + } + + boolean isClosed() { + return state == STATUS_CLOSED; + } + + boolean isRetired() { + return state == STATUS_RETIRED; + } + + boolean isDead() { + return state == STATUS_DEAD; + } + + boolean isOpen() { + return state == STATUS_OPEN; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableItem.java b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableItem.java new file mode 100644 index 00000000000..d6a46fbb45f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableItem.java @@ -0,0 +1,11 @@ +package com.orientechnologies.common.collection.closabledictionary; + +/** + * Item is going to be stored inside of {@link OClosableLinkedContainer}. + * This interface presents item that may be in two states open and closed. + */ +public interface OClosableItem { + boolean isOpen(); + void close(); + void open(); +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLRUList.java b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLRUList.java new file mode 100644 index 00000000000..fddbb38aada --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLRUList.java @@ -0,0 +1,221 @@ +package com.orientechnologies.common.collection.closabledictionary; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * LRU list is used inside of {@link OClosableLinkedContainer}. + * + * @param Key type + * @param Value type + */ +class OClosableLRUList implements Iterable> { + private int size; + + private OClosableEntry head; + private OClosableEntry tail; + + void remove(OClosableEntry entry) { + final OClosableEntry next = entry.getNext(); + final OClosableEntry prev = entry.getPrev(); + + if (!(next != null || prev != null || entry == head)) + return; + + if (prev != null) { + assert prev.getNext() == entry; + } + + if (next != null) { + assert next.getPrev() == entry; + } + + if (next != null) { + next.setPrev(prev); + } + + if (prev != null) { + prev.setNext(next); + } + + if (head == entry) { + assert entry.getPrev() == null; + head = next; + } + + if (tail == entry) { + assert entry.getNext() == null; + tail = prev; + } + + entry.setNext(null); + entry.setPrev(null); + + size--; + } + + boolean contains(OClosableEntry entry) { + return entry.getNext() != null || entry.getPrev() != null || entry == head; + } + + void moveToTheTail(OClosableEntry entry) { + if (tail == entry) { + assert entry.getNext() == null; + return; + } + + final OClosableEntry next = entry.getNext(); + final OClosableEntry prev = entry.getPrev(); + + boolean newEntry = !(next != null || prev != null || entry == head); + + if (prev != null) { + assert prev.getNext() == entry; + } + + if (next != null) { + assert next.getPrev() == entry; + } + + if (prev != null) { + prev.setNext(next); + } + + if (next != null) { + next.setPrev(prev); + } + + if (head == entry) { + assert entry.getPrev() == null; + head = next; + } + + entry.setPrev(tail); + entry.setNext(null); + if (tail != null) { + assert tail.getNext() == null; + tail.setNext(entry); + tail = entry; + } else { + tail = head = entry; + } + + if (newEntry) + size++; + } + + int size() { + return size; + } + + OClosableEntry poll() { + if (head == null) + return null; + + final OClosableEntry entry = head; + + OClosableEntry next = head.getNext(); + assert next == null || next.getPrev() == head; + + head = next; + if (next != null) { + next.setPrev(null); + } + + assert head == null || head.getPrev() == null; + + if (head == null) + tail = null; + + entry.setNext(null); + assert entry.getPrev() == null; + + size--; + + return entry; + } + + /** + * @return Iterator to iterate from head to the tail. + */ + public Iterator> iterator() { + return new Iterator>() { + private OClosableEntry next = head; + private OClosableEntry current = null; + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public OClosableEntry next() { + if (next == null) { + throw new NoSuchElementException(); + } + + current = next; + next = next.getNext(); + return current; + } + + @Override + public void remove() { + if (current == null) { + throw new IllegalStateException("Method next was not called"); + } + + OClosableLRUList.this.remove(current); + current = null; + } + }; + } + + boolean assertForwardStructure() { + if (head == null) + return tail == null; + + OClosableEntry current = head; + + while (current.getNext() != null) { + OClosableEntry prev = current.getPrev(); + OClosableEntry next = current.getNext(); + + if (prev != null) { + assert prev.getNext() == current; + } + + if (next != null) { + assert next.getPrev() == current; + } + + current = current.getNext(); + } + + return current == tail; + } + + boolean assertBackwardStructure() { + if (tail == null) + return head == null; + + OClosableEntry current = tail; + + while (current.getPrev() != null) { + OClosableEntry prev = current.getPrev(); + OClosableEntry next = current.getNext(); + + if (prev != null) { + assert prev.getNext() == current; + } + + if (next != null) { + assert next.getPrev() == current; + } + + current = current.getPrev(); + } + + return current == head; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLinkedContainer.java b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLinkedContainer.java new file mode 100644 index 00000000000..972c0d4af3d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/collection/closabledictionary/OClosableLinkedContainer.java @@ -0,0 +1,879 @@ +package com.orientechnologies.common.collection.closabledictionary; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import javax.annotation.concurrent.GuardedBy; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Container for the elements which may be in open/closed state. But only limited amount of elements are hold by given container may + * be in open state. + *

+ * Elements may be added in this container only in open state,as result after addition of some elements other rarely used elements + * will be closed. + *

+ * When you want to use elements from container you should acquire them {@link #acquire(Object)}. So element still will be inside of + * container but in acquired state. As result it will not be automatically closed when container will try to close rarely used + * items. + *

+ * Container uses LRU eviction policy to choose item to be closed. + *

+ * When you finish to work with element from container you should release (@link {@link #release(OClosableEntry)}) element back to + * container. + * + * @param Key associated with entry stored inside of container. + * @param Value which may be in open/closed stated and associated with key. + */ +public class OClosableLinkedContainer { + /* + * Design of container consist of several major parts. + * + * Operation buffers. + * + * Operation buffers are needed to log all events inside of container which may cause changes in content and order + * of LRU items. Following operations are logged: + * 1. Add. + * 2. Remove. + * 3. Acquire. + * + * Instead of logging all of these operations using single buffer (logger) we split logging by several buffers. + * So only few threads are logging at any buffer at the same moment reducing chance of contention. + * + * There are two types of buffers : state buffer and read buffers. State buffer is used to log operations which can not be lost such + * as add and remove. Read buffers are used to log operations small part of which may be lost. + * + * As result write buffer is implemented as concurrent linked queue and read buffers are implemented as arrays with + * two types of counters. So in nutshell read buffer is array based implementation of ring buffer. + * Counters have following meaning : write counter - next position inside of array which is used to write in buffer entry + * which was accessed as result of acquire operation, read counter - next position inside of array which is used to read data from + * buffer during flushing of data. So this buffer is the implementation of Lamport queue algorithm not taking into account that we may work + * with several producers. To decrease contention between threads we do not perform CAS operations on write counter during + * operation logging. As result threads may overwrite logs of each other which is acceptable because part of statistic + * may be lost. + * + * Content of all buffers is processed (flushed) when one of the read buffers is reached threshold between position of write + * counter during last buffer flush (this position is stored at "drain at write count" field associated with each buffer) + * and current position of write counter. Buffers also flushed when we log any operation in write buffer. + * + * There is no common lock between of operations with data and logging of those operations to buffers so related records can be reordered + * during processing of buffers. For example we may add item in t1 thread and remove item in t2 thread. But operations logged in buffers + * may be processed in different order and record removed from cache may stay in LRU list forever. To avoid given situation state machine + * was introduced. + * + * Each entry has following states: open, closed, retired (removed from map but not from LRU list), dead(completely removed), + * acquired. + * + * Following state transitions are allowed: + * + * open->(evict() method is called during buffer flush)->close + * closed->(acquire() method is called)->acquired + * acquired->(release())->open + * + * open->(remove())->retired + * closed->(remove())->retired + * acquired->(remove())->retired + * + * It is seen from state flow that it is impossible to close item in "acquired" state. + * Also during of processing of "add" operation, item will be added into LRU list only if it is in + * + * LRU list is modified during flush of the buffers , to make those modifications safe flush of the buffer + * is performed under exclusive lock. To avoid contention between threads any thread which has been noticed that read buffer should + * be drained calls tryLock but not lock operation. + */ + + /** + * The number of CPUs + */ + private static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * The number of read buffers to use. + */ + private static final int NUMBER_OF_READ_BUFFERS = closestPowerOfTwo(NCPU); + + /** + * Mask value for indexing into the read buffers. + */ + private static final int READ_BUFFERS_MASK = NUMBER_OF_READ_BUFFERS - 1; + + /** + * The number of pending read operations before attempting to drain. + */ + private static final int READ_BUFFER_THRESHOLD = 32; + + /** + * The maximum number of read operations to perform per amortized drain. + */ + private static final int READ_BUFFER_DRAIN_THRESHOLD = 2 * READ_BUFFER_THRESHOLD; + + /** + * The maximum number of write operations to perform per amortized drain. + */ + private static final int WRITE_BUFFER_DRAIN_THRESHOLD = 32; + + /** + * The maximum number of pending reads per buffer. + */ + private static final int READ_BUFFER_SIZE = 2 * READ_BUFFER_DRAIN_THRESHOLD; + + /** + * Mask value for indexing into the read buffer. + */ + private static final int READ_BUFFER_INDEX_MASK = READ_BUFFER_SIZE - 1; + + /** + * Last indexes of buffers on which buffer flush procedure was stopped + */ + @GuardedBy("lruLock") + private final long[] readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS]; + + /** + * Next indexes of buffers on which threads will write new entries during logging of acquire operations + */ + private final AtomicLong[] readBufferWriteCount; + + /** + * Values of {@link #readBufferWriteCount} on which buffers were flushed. + */ + private final AtomicLong[] readBufferDrainAtWriteCount; + + /** + * Read buffers are used to lot {@link #acquire(Object)} operations when item is not switched from closed to open states. + * So in cases when amount of items inside of {@link #lruList} is not going to be changed, but only information about recency + * of items should be modified. + */ + private final AtomicReference>[][] readBuffers; + + /** + * Lock which wraps all buffer flush operations and as result protects changes of {@link #lruList}. + */ + private final Lock lruLock = new ReentrantLock(); + + /** + * LRU list to updated statistic of recency of contained items. + */ + @GuardedBy("lruLock") + private final OClosableLRUList lruList = new OClosableLRUList(); + + /** + * Main source of truth of container if value is absent in this field it is absent in container. + */ + private final ConcurrentHashMap> data = new ConcurrentHashMap>(); + + /** + * Buffer which contains operation which includes changes of states from closed to open, and from any state to retired. + * In other words this buffer contains information about operations which affect amount of items inside of {@link #lruList} and + * those operations can not be lost. + */ + private final ConcurrentLinkedQueue stateBuffer = new ConcurrentLinkedQueue(); + + /** + * Maximum amount of open items inside of container. + */ + private final int openLimit; + + /** + * Status which indicates whether flush of buffers should be performed or may be delayed. + */ + private final AtomicReference drainStatus = new AtomicReference(DrainStatus.IDLE); + + /** + * Latch which prevents addition or open of new files if limit of open files is reached + */ + private final AtomicReference openLatch = new AtomicReference(); + + /** + * Amount of simultaneously open files in container + */ + private final AtomicInteger openFiles = new AtomicInteger(); + + /** + * Creates new instance of container and set limit of open files which may be hold by container. + * + * @param openLimit Limit of open files hold by container. + */ + public OClosableLinkedContainer(final int openLimit) { + this.openLimit = openLimit; + + AtomicLong[] rbwc = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + AtomicLong[] rbdawc = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + AtomicReference>[][] rbs = new AtomicReference[NUMBER_OF_READ_BUFFERS][]; + + for (int i = 0; i < NUMBER_OF_READ_BUFFERS; i++) { + rbwc[i] = new AtomicLong(); + rbdawc[i] = new AtomicLong(); + + rbs[i] = new AtomicReference[READ_BUFFER_SIZE]; + for (int n = 0; n < READ_BUFFER_SIZE; n++) { + rbs[i][n] = new AtomicReference>(); + } + } + + readBufferWriteCount = rbwc; + readBufferDrainAtWriteCount = rbdawc; + readBuffers = rbs; + } + + /** + * Adds item to the container. + * Item should be in open state. + * + * @param key Key associated with given item. + * @param item Item associated with passed in key. + */ + public void add(K key, V item) throws InterruptedException { + if (!item.isOpen()) + throw new IllegalArgumentException("All passed in items should be in open state"); + + checkOpenFilesLimit(); + + final OClosableEntry closableEntry = new OClosableEntry(item); + final OClosableEntry oldEntry = data.putIfAbsent(key, closableEntry); + + if (oldEntry != null) { + throw new IllegalStateException("Item with key " + key + " already exists"); + } + + logAdd(closableEntry); + } + + /** + * Removes item associated with passed in key. + * + * @param key Key associated with item to remove. + * + * @return Removed item. + */ + public V remove(K key) { + final OClosableEntry removed = data.remove(key); + + if (removed != null) { + long preStatus = removed.makeRetired(); + + if (OClosableEntry.isOpen(preStatus)) { + countClosedFiles(); + } + + logRemoved(removed); + return removed.get(); + } + + return null; + } + + /** + * Acquires item associated with passed in key in container. + * It is guarantied that item will not be closed if limit of open items will be exceeded and container will close rarely used + * items. + * + * @param key Key associated with item + * + * @return Acquired item if key exists into container or null if there is no item associated with given container + */ + public OClosableEntry acquire(K key) throws InterruptedException { + checkOpenFilesLimit(); + + final OClosableEntry entry = data.get(key); + + if (entry == null) + return null; + + boolean logOpen = false; + entry.acquireStateLock(); + try { + if (entry.isRetired() || entry.isDead()) { + return null; + } else if (entry.isClosed()) { + entry.makeAcquiredFromClosed(entry.get()); + logOpen = true; + } else if (entry.isOpen()) { + entry.makeAcquiredFromOpen(); + } else { + entry.incrementAcquired(); + } + } finally { + entry.releaseStateLock(); + } + + if (logOpen) { + logOpen(entry); + } else { + logAcquire(entry); + } + + assert entry.get().isOpen(); + return entry; + } + + /** + * Checks if containers limit of open files is reached. + *

+ * In such case execution of threads which add or acquire items is stopped and they wait till buffers will be emptied + * and nubmer of open files will be inside limit. + */ + private void checkOpenFilesLimit() throws InterruptedException { + CountDownLatch ol = openLatch.get(); + if (ol != null) + ol.await(); + + while (openFiles.get() > openLimit) { + final CountDownLatch latch = new CountDownLatch(1); + + //make other threads to wait till we evict entries and close evicted open files + if (openLatch.compareAndSet(null, latch)) { + while (openFiles.get() > openLimit) { + emptyBuffers(); + } + + latch.countDown(); + openLatch.set(null); + } else { + ol = openLatch.get(); + + if (ol != null) + ol.await(); + } + } + } + + /** + * Releases item acquired by call of {@link #acquire(Object)} method. + * After this call container is free to close given item if limit of open files exceeded and this item is rarely used. + * + * @param entry Entry to release + */ + public void release(OClosableEntry entry) { + entry.releaseAcquired(); + } + + /** + * Returns item without acquiring it. State of item is not guarantied in such case. + * + * @param key Key associated with required item. + * + * @return Item associated with given key. + */ + public V get(K key) { + final OClosableEntry entry = data.get(key); + if (entry != null) + return entry.get(); + + return null; + } + + /** + * Clears all content. + */ + public void clear() { + lruLock.lock(); + try { + data.clear(); + + for (int n = 0; n < NUMBER_OF_READ_BUFFERS; n++) { + final AtomicReference>[] buffer = readBuffers[n]; + for (int i = 0; i < READ_BUFFER_SIZE; i++) { + buffer[i].set(null); + } + + readBufferReadCount[n] = 0; + readBufferWriteCount[n].set(0); + readBufferDrainAtWriteCount[n].set(0); + } + + openFiles.set(0); + stateBuffer.clear(); + + while (lruList.poll() != null) + ; + } finally { + lruLock.unlock(); + } + } + + /** + * Closes item related to passed in key. + * Item will be closed if it exists and is not acquired. + * + * @param key Key related to item that has going to be closed. + * + * @return true if item was closed and false otherwise. + */ + public boolean close(K key) { + emptyBuffers(); + + final OClosableEntry entry = data.get(key); + if (entry == null) + return true; + + if (entry.makeClosed()) { + countClosedFiles(); + + return true; + } + + return false; + } + + boolean checkAllLRUListItemsInMap() { + lruLock.lock(); + try { + emptyWriteBuffer(); + emptyReadBuffers(); + + for (OClosableEntry entry : lruList) { + boolean result = data.containsValue(entry); + if (!result) + return false; + } + + } finally { + lruLock.unlock(); + } + + return true; + } + + boolean checkLRUSize() { + return lruList.size() <= openLimit; + } + + boolean checkLRUSizeEqualsToCapacity() { + return lruList.size() == openLimit; + } + + int checkAllOpenItemsInLRUList() { + int count = 0; + + lruLock.lock(); + try { + emptyWriteBuffer(); + emptyReadBuffers(); + + for (OClosableEntry entry : data.values()) { + boolean contains = false; + + if (!entry.get().isOpen()) + continue; + + for (OClosableEntry lruEntry : lruList) { + if (lruEntry == entry) { + contains = true; + } + } + + if (!contains) + count++; + } + } finally { + lruLock.unlock(); + } + + return count; + } + + boolean checkNoClosedItemsInLRUList() { + lruLock.lock(); + try { + emptyWriteBuffer(); + emptyReadBuffers(); + + for (OClosableEntry entry : data.values()) { + boolean contains = false; + + if (entry.get().isOpen()) + continue; + + for (OClosableEntry lruEntry : lruList) { + if (lruEntry == entry) { + contains = true; + } + } + + if (contains) + return false; + } + } finally { + lruLock.unlock(); + } + + return true; + } + + /** + * Read content of write buffer and adds/removes LRU entries to update internal statistic. + * Method has to be wrapped by LRU lock. + */ + @GuardedBy("lruLock") + private void emptyWriteBuffer() { + Runnable task = stateBuffer.poll(); + while (task != null) { + task.run(); + task = stateBuffer.poll(); + } + } + + /** + * Read content of all read buffers and reorder elements inside of LRU list to update internal statistic. + * Method has to be wrapped by LRU lock. + */ + @GuardedBy("lruLock") + private void emptyReadBuffers() { + for (int n = 0; n < NUMBER_OF_READ_BUFFERS; n++) { + AtomicReference>[] buffer = readBuffers[n]; + + long writeCount = readBufferDrainAtWriteCount[n].get(); + long counter = readBufferReadCount[n]; + + while (true) { + final int bufferIndex = (int) (counter & READ_BUFFER_INDEX_MASK); + final AtomicReference> eref = buffer[bufferIndex]; + final OClosableEntry entry = eref.get(); + + if (entry == null) + break; + + applyRead(entry); + counter++; + + eref.lazySet(null); + } + + readBufferReadCount[n] = counter; + readBufferDrainAtWriteCount[n].lazySet(writeCount); + } + } + + void emptyBuffers() { + lruLock.lock(); + try { + emptyWriteBuffer(); + emptyReadBuffers(); + } finally { + lruLock.unlock(); + } + + } + + /** + * Put the entry to the tail of LRU list if entry is not in "retired" or "acquired" state. + * + * @param entry Entry to process. + */ + private void logOpen(OClosableEntry entry) { + afterWrite(new LogOpen(entry)); + + countOpenFiles(); + } + + /** + * Put the entry at the tail of LRU list if if entry is not in "retired" or "acquired" state. + * + * @param entry LRU entry + */ + private void logAdd(OClosableEntry entry) { + afterWrite(new LogAdd(entry)); + + countOpenFiles(); + } + + /** + * Put entry at the tail of LRU list if entry is already inside of LRU list. + * + * @param entry LRU entry + */ + private void logAcquire(OClosableEntry entry) { + afterRead(entry); + } + + /** + * Remove LRU entry from the LRU list. + * + * @param entry LRU entry. + */ + private void logRemoved(OClosableEntry entry) { + afterWrite(new LogRemoved(entry)); + } + + /** + * Method is used to log operations which change content of the container. + * Such changes should be flushed immediately to update content of LRU list. + * + * @param task Task which contains code is used to manipulate LRU list + */ + private void afterWrite(Runnable task) { + stateBuffer.add(task); + drainStatus.lazySet(DrainStatus.REQUIRED); + tryToDrainBuffers(); + } + + /** + * Method is used to log operations which do not change LRU list content but affect order of items inside of LRU list. + * Such changes may be delayed till buffer will be full. + * + * @param entry Entry which was affected by operation. + */ + private void afterRead(OClosableEntry entry) { + final int bufferIndex = readBufferIndex(); + final long writeCount = putEntryInReadBuffer(entry, bufferIndex); + drainReadBuffersIfNeeded(bufferIndex, writeCount); + } + + /** + * Adds entry to the read buffer with selected index and returns amount of writes to this buffer since creation of this container. + * + * @param entry LRU entry to add. + * @param bufferIndex Index of buffer + * + * @return Amount of writes to the buffer since creation of this container. + */ + private long putEntryInReadBuffer(OClosableEntry entry, int bufferIndex) { + //next index to write for this buffer + AtomicLong writeCounter = readBufferWriteCount[bufferIndex]; + final long counter = writeCounter.get(); + + //we do not use CAS operations to limit contention between threads + //it is normal that because of duplications of indexes some of items will be lost + writeCounter.lazySet(counter + 1); + + final AtomicReference>[] buffer = readBuffers[bufferIndex]; + AtomicReference> bufferEntry = buffer[(int) (counter & READ_BUFFER_INDEX_MASK)]; + bufferEntry.lazySet(entry); + + return counter + 1; + } + + /** + * @param bufferIndex Read buffer index + * @param writeCount Amount of writes performed for given buffer + */ + private void drainReadBuffersIfNeeded(int bufferIndex, long writeCount) { + //amount of writes to the buffer at the last time when buffer was flushed + final AtomicLong lastDrainWriteCount = readBufferDrainAtWriteCount[bufferIndex]; + final boolean bufferOverflow = (writeCount - lastDrainWriteCount.get()) > READ_BUFFER_THRESHOLD; + + if (drainStatus.get().shouldBeDrained(bufferOverflow)) { + tryToDrainBuffers(); + } + } + + private void tryToDrainBuffers() { + if (lruLock.tryLock()) { + try { + //optimization to avoid to call tryLock if it is not needed + drainStatus.lazySet(DrainStatus.IN_PROGRESS); + drainBuffers(); + } finally { + //cas operation because we do not want to overwrite REQUIRED status and to avoid false optimization of + //drain buffer by IN_PROGRESS status + drainStatus.compareAndSet(DrainStatus.IN_PROGRESS, DrainStatus.IDLE); + lruLock.unlock(); + } + } + } + + private void drainBuffers() { + drainWriteBuffer(); + drainReadBuffers(); + } + + private void drainReadBuffers() { + final long threadId = Thread.currentThread().getId(); + for (long n = threadId; n < threadId + NUMBER_OF_READ_BUFFERS; n++) { + drainReadBuffer((int) (n & READ_BUFFERS_MASK)); + } + } + + private void drainReadBuffer(int bufferIndex) { + //amount of writes to the buffer at the moment + final long bufferWriteCount = readBufferWriteCount[bufferIndex].get(); + final AtomicReference>[] buffer = readBuffers[bufferIndex]; + //position of previous flush + long bufferCounter = readBufferReadCount[bufferIndex]; + + for (int n = 0; n < READ_BUFFER_DRAIN_THRESHOLD; n++) { + final int entryIndex = (int) (bufferCounter & READ_BUFFER_INDEX_MASK); + final AtomicReference> bufferEntry = buffer[entryIndex]; + final OClosableEntry entry = bufferEntry.get(); + if (entry == null) + break; + + bufferCounter++; + applyRead(entry); + + //GC optimization + bufferEntry.lazySet(null); + } + + readBufferReadCount[bufferIndex] = bufferCounter; + readBufferDrainAtWriteCount[bufferIndex].lazySet(bufferWriteCount); + } + + private void applyRead(OClosableEntry entry) { + if (lruList.contains(entry)) { + lruList.moveToTheTail(entry); + } + evict(); + } + + private void drainWriteBuffer() { + for (int i = 0; i < WRITE_BUFFER_DRAIN_THRESHOLD; i++) { + Runnable task = stateBuffer.poll(); + if (task == null) + break; + + task.run(); + } + } + + private void countOpenFiles() { + openFiles.incrementAndGet(); + } + + private void countClosedFiles() { + openFiles.decrementAndGet(); + } + + /** + * Finds closest power of two for given integer value. Idea is simple duplicate the most significant bit to the lowest bits for + * the smallest number of iterations possible and then increment result value by 1. + * + * @param value Integer the most significant power of 2 should be found. + * + * @return The most significant power of 2. + */ + private static int closestPowerOfTwo(int value) { + int n = value - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= (1 << 30)) ? 1 << 30 : n + 1; + } + + private static int readBufferIndex() { + //partition buffers between threads + final long threadId = Thread.currentThread().getId(); + return (int) (threadId & READ_BUFFERS_MASK); + } + + private void evict() { + final long start = Orient.instance().getProfiler().startChrono(); + + final int initialSize = lruList.size(); + int closedFiles = 0; + + while (lruList.size() > openLimit) { + //we may only close items in open state so we "peek" them first + Iterator> iterator = lruList.iterator(); + + boolean entryClosed = false; + + while (iterator.hasNext()) { + OClosableEntry entry = iterator.next(); + if (entry.makeClosed()) { + closedFiles++; + iterator.remove(); + entryClosed = true; + + countClosedFiles(); + break; + } + } + + //there are no items in open state stop eviction + if (!entryClosed) + break; + } + + if (closedFiles > 0) { + OLogManager.instance().debug(this, + "Reached maximum of opened files %d (max=%d), closed %d files. Consider to raise this limit by increasing the global setting '%s' and the OS limit on opened files per processor", + initialSize, openLimit, closedFiles, OGlobalConfiguration.OPEN_FILES_LIMIT.getKey()); + } + + Orient.instance().getProfiler() + .stopChrono("disk.closeFiles", "Close the opened files because reached the configured limit", start); + } + + private class LogAdd implements Runnable { + private final OClosableEntry entry; + + private LogAdd(OClosableEntry entry) { + this.entry = entry; + } + + @Override + public void run() { + //despite of the fact that status can be change it is safe to proceed because it means + //that LogRemove entree will be after LogAdd entree (we call markRetired firs and then only log entry removal) + if (!entry.isDead() && !entry.isRetired()) { + lruList.moveToTheTail(entry); + evict(); + } + } + } + + private class LogRemoved implements Runnable { + private final OClosableEntry entry; + + private LogRemoved(OClosableEntry entry) { + this.entry = entry; + } + + @Override + public void run() { + if (entry.isRetired()) { + lruList.remove(entry); + entry.makeDead(); + } + } + } + + private class LogOpen implements Runnable { + private final OClosableEntry entry; + + private LogOpen(OClosableEntry entry) { + this.entry = entry; + } + + @Override + public void run() { + if (!entry.isRetired() && !entry.isDead()) { + lruList.moveToTheTail(entry); + evict(); + } + } + } + + private enum DrainStatus { + IDLE { + @Override + boolean shouldBeDrained(boolean readBufferOverflow) { + return readBufferOverflow; + } + }, IN_PROGRESS { + @Override + boolean shouldBeDrained(boolean readBufferOverflow) { + return false; + } + }, REQUIRED { + @Override + boolean shouldBeDrained(boolean readBufferOverflow) { + return true; + } + }; + + abstract boolean shouldBeDrained(boolean readBufferOverflow); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/comparator/OByteArrayComparator.java b/core/src/main/java/com/orientechnologies/common/comparator/OByteArrayComparator.java new file mode 100644 index 00000000000..3013f85e5d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/comparator/OByteArrayComparator.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.comparator; + +import java.util.Comparator; + + +/** + * Comparator for byte arrays comparison. Bytes are compared like unsigned not like signed bytes. + * + * @author Andrey Lomakin + * @since 03.07.12 + */ +public class OByteArrayComparator implements Comparator { + public static final OByteArrayComparator INSTANCE = new OByteArrayComparator(); + + public int compare(final byte[] arrayOne, final byte[] arrayTwo) { + final int lenDiff = arrayOne.length - arrayTwo.length; + + if (lenDiff != 0) + return lenDiff; + + for (int i = 0; i < arrayOne.length; i++) { + final int valOne = arrayOne[i] & 0xFF; + final int valTwo = arrayTwo[i] & 0xFF; + + final int diff = valOne - valTwo; + if (diff != 0) + return diff; + } + + return 0; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/comparator/OCaseInsentiveComparator.java b/core/src/main/java/com/orientechnologies/common/comparator/OCaseInsentiveComparator.java new file mode 100755 index 00000000000..29fb8a087ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/comparator/OCaseInsentiveComparator.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.comparator; + +import java.util.Comparator; + +/** + * Compares strings without taking into account their case. + */ +public class OCaseInsentiveComparator implements Comparator { + public int compare(final String stringOne, final String stringTwo) { + return stringOne.compareToIgnoreCase(stringTwo); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/comparator/OComparatorFactory.java b/core/src/main/java/com/orientechnologies/common/comparator/OComparatorFactory.java new file mode 100755 index 00000000000..97605140841 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/comparator/OComparatorFactory.java @@ -0,0 +1,72 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.comparator; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import java.util.Comparator; + +/** + * Creates comparators for classes that does not implement {@link Comparable} but logically can be compared. + * + * @author Andrey Lomakin + * @since 03.07.12 + */ +public class OComparatorFactory { + public static final OComparatorFactory INSTANCE = new OComparatorFactory(); + + private static final boolean unsafeWasDetected; + + static { + boolean unsafeDetected = false; + + try { + Class sunClass = Class.forName("sun.misc.Unsafe"); + unsafeDetected = sunClass != null; + } catch (ClassNotFoundException cnfe) { + // Ignore + } + + unsafeWasDetected = unsafeDetected; + } + + /** + * Returns {@link Comparator} instance if applicable one exist or null otherwise. + * + * @param clazz + * Class of object that is going to be compared. + * @param + * Class of object that is going to be compared. + * @return {@link Comparator} instance if applicable one exist or null otherwise. + */ + @SuppressWarnings("unchecked") + public Comparator getComparator(Class clazz) { + boolean useUnsafe = OGlobalConfiguration.MEMORY_USE_UNSAFE.getValueAsBoolean(); + + if (clazz.equals(byte[].class)) { + if (useUnsafe && unsafeWasDetected) + return (Comparator) OUnsafeByteArrayComparator.INSTANCE; + + return (Comparator) OByteArrayComparator.INSTANCE; + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/comparator/ODefaultComparator.java b/core/src/main/java/com/orientechnologies/common/comparator/ODefaultComparator.java new file mode 100644 index 00000000000..48d7608f841 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/comparator/ODefaultComparator.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.comparator; + +import java.util.Comparator; + +/** + * Comparator that calls {@link Comparable#compareTo(Object)} methods for getting results for all {@link Comparable} types. + * Otherwise result of {@link Comparator} that returned from {@link OComparatorFactory} will be used. + * + * The special case is null values. Null is treated as smallest value against other values. If both arguments are null they are + * treated as equal. + * + * @author Andrey Lomakin + * @since 03.07.12 + */ +public class ODefaultComparator implements Comparator { + public static final ODefaultComparator INSTANCE = new ODefaultComparator(); + + @SuppressWarnings("unchecked") + public int compare(final Object objectOne, final Object objectTwo) { + if (objectOne == null) { + if (objectTwo == null) + return 0; + else + return -1; + } else if (objectTwo == null) + return 1; + + if (objectOne == objectTwo) + // FAST COMPARISON + return 0; + + if (objectOne instanceof Comparable) + return ((Comparable) objectOne).compareTo(objectTwo); + + final Comparator comparator = OComparatorFactory.INSTANCE.getComparator(objectOne.getClass()); + + if (comparator != null) + return ((Comparator) comparator).compare(objectOne, objectTwo); + + throw new IllegalStateException("Object of class '" + objectOne.getClass().getName() + "' cannot be compared"); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/comparator/OUnsafeByteArrayComparator.java b/core/src/main/java/com/orientechnologies/common/comparator/OUnsafeByteArrayComparator.java new file mode 100755 index 00000000000..ab8be6b040c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/comparator/OUnsafeByteArrayComparator.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.comparator; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Comparator; + +import sun.misc.Unsafe; + +/** + * Comparator for fast byte arrays comparison using {@link Unsafe} class. Bytes are compared like unsigned not like signed bytes. + * + * + * @author Andrey Lomakin + * @since 08.07.12 + */ +@SuppressWarnings("restriction") +public class OUnsafeByteArrayComparator implements Comparator { + public static final OUnsafeByteArrayComparator INSTANCE = new OUnsafeByteArrayComparator(); + + private static final Unsafe unsafe; + + private static final int BYTE_ARRAY_OFFSET; + private static final boolean littleEndian = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN); + + private static final int LONG_SIZE = Long.SIZE / Byte.SIZE; + + static { + unsafe = (Unsafe) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return f.get(null); + } catch (NoSuchFieldException e) { + throw new Error(e); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + }); + + BYTE_ARRAY_OFFSET = unsafe.arrayBaseOffset(byte[].class); + + final int byteArrayScale = unsafe.arrayIndexScale(byte[].class); + + if (byteArrayScale != 1) + throw new Error(); + + } + + public int compare(byte[] arrayOne, byte[] arrayTwo) { + if (arrayOne.length > arrayTwo.length) + return 1; + + if (arrayOne.length < arrayTwo.length) + return -1; + + final int WORDS = arrayOne.length / LONG_SIZE; + + for (int i = 0; i < WORDS * LONG_SIZE; i += LONG_SIZE) { + final long index = i + BYTE_ARRAY_OFFSET; + + final long wOne = unsafe.getLong(arrayOne, index); + final long wTwo = unsafe.getLong(arrayTwo, index); + + if (wOne == wTwo) + continue; + + if (littleEndian) + return lessThanUnsigned(Long.reverseBytes(wOne), Long.reverseBytes(wTwo)) ? -1 : 1; + + return lessThanUnsigned(wOne, wTwo) ? -1 : 1; + } + + for (int i = WORDS * LONG_SIZE; i < arrayOne.length; i++) { + int diff = compareUnsignedByte(arrayOne[i], arrayTwo[i]); + if (diff != 0) + return diff; + } + + return 0; + } + + private static boolean lessThanUnsigned(long longOne, long longTwo) { + return (longOne + Long.MIN_VALUE) < (longTwo + Long.MIN_VALUE); + } + + private static int compareUnsignedByte(byte byteOne, byte byteTwo) { + final int valOne = byteOne & 0xFF; + final int valTwo = byteTwo & 0xFF; + return valOne - valTwo; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/ONeedRetryException.java b/core/src/main/java/com/orientechnologies/common/concur/ONeedRetryException.java new file mode 100755 index 00000000000..ed41b500654 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/ONeedRetryException.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur; + +import com.orientechnologies.orient.core.exception.OCoreException; + +/** + * Abstract base exception to extend for all the exception that report to the user it has been thrown but re-executing it could + * succeed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class ONeedRetryException extends OCoreException { + private static final long serialVersionUID = 1L; + + protected ONeedRetryException(final ONeedRetryException exception) { + super(exception); + } + + protected ONeedRetryException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/OOfflineNodeException.java b/core/src/main/java/com/orientechnologies/common/concur/OOfflineNodeException.java new file mode 100644 index 00000000000..3164ae58bca --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/OOfflineNodeException.java @@ -0,0 +1,17 @@ +package com.orientechnologies.common.concur; + +import com.orientechnologies.common.exception.OSystemException; + +/** + * Created by tglman on 29/12/15. + */ +public class OOfflineNodeException extends OSystemException { + public OOfflineNodeException(OOfflineNodeException exception) { + super(exception); + } + + public OOfflineNodeException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/OTimeoutException.java b/core/src/main/java/com/orientechnologies/common/concur/OTimeoutException.java new file mode 100755 index 00000000000..fd1f84b1f30 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/OTimeoutException.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur; + +import com.orientechnologies.common.exception.OSystemException; + +/** + * Timeout exception. The acquiring of a shared resource caused a timeout. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OTimeoutException extends OSystemException { + private static final long serialVersionUID = 1L; + + public OTimeoutException(OTimeoutException exception) { + super(exception); + } + + public OTimeoutException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/executors/SubExecutorService.java b/core/src/main/java/com/orientechnologies/common/concur/executors/SubExecutorService.java new file mode 100644 index 00000000000..54c1aee3d48 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/executors/SubExecutorService.java @@ -0,0 +1,457 @@ +/* + * + * * Copyright 2010-2016 OrientDB LTD (http://orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ + +package com.orientechnologies.common.concur.executors; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Limits the tasks scope of an {@link ExecutorService} into a smaller sub-executor service. This allows to submit tasks to the + * underlying executor service while the shutdown related methods are scoped only to the subset of tasks submitted through this + * sub-executor: + *
    + *
  • {@link #shutdown()} – shutdowns this sub-executor only. + *
  • {@link #isShutdown()} and {@link #isTerminated()} – report status of this sub-executor only. + *
  • {@link #awaitTermination(long, TimeUnit)} – awaits for tasks submitted through this sub-executor only. + *
+ * + * @author Sergey Sitnikov + */ +@SuppressWarnings({ "unchecked", "NullableProblems" }) +public class SubExecutorService implements ExecutorService { + + private final ExecutorService executorService; + + private boolean alive = true; + + private final Lock aliveLock = new ReentrantLock(); + private final Condition terminated = aliveLock.newCondition(); + + private final Set tasks = new HashSet(); + + /** + * Constructs a new SubExecutorService for the given executor service. + * + * @param executorService the underlying executor service to submit tasks to + */ + public SubExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + @Override + public void shutdown() { + acquireAlive(); + try { + alive = false; + shutdownTasks(tasks); + } finally { + releaseAlive(); + } + } + + @Override + public List shutdownNow() { + throw new UnsupportedOperationException("shutdownNow is not supported"); + } + + @Override + public boolean isShutdown() { + acquireAlive(); + try { + return !isAlive(); + } finally { + releaseAlive(); + } + } + + @Override + public boolean isTerminated() { + acquireAlive(); + try { + return !isRunning(); + } finally { + releaseAlive(); + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + acquireAlive(); + try { + return !isRunning() || terminated.await(timeout, unit); + } finally { + releaseAlive(); + } + } + + @Override + public Future submit(Callable task) { + acquireAlive(); + try { + if (isAlive()) { + final Task wrapped = new CallableTask(task, true); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().submit((Callable) wrapped)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(task); + } finally { + releaseAlive(); + } + } + + @Override + public Future submit(Runnable task, T result) { + acquireAlive(); + try { + if (isAlive()) { + final Task wrapped = new RunnableTask(task, true); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().submit(wrapped, result)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(task); + } finally { + releaseAlive(); + } + } + + @Override + public Future submit(Runnable task) { + acquireAlive(); + try { + if (isAlive()) { + final Task wrapped = new RunnableTask(task, true); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().submit((Runnable) wrapped)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(task); + } finally { + releaseAlive(); + } + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + throw new UnsupportedOperationException("invokeAll is not supported"); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + throw new UnsupportedOperationException("invokeAll is not supported"); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + throw new UnsupportedOperationException("invokeAny is not supported"); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + throw new UnsupportedOperationException("invokeAny is not supported"); + } + + @Override + public void execute(Runnable command) { + submit(command); + } + + @Override + public String toString() { + return "Sub(" + getExecutorService().toString() + ")"; + } + + protected void acquireAlive() { + aliveLock.lock(); + } + + protected void releaseAlive() { + aliveLock.unlock(); + } + + protected boolean isAlive() { + return alive; + } + + protected boolean isRunning() { + return isAlive() || !tasks.isEmpty(); + } + + protected ExecutorService getExecutorService() { + return executorService; + } + + protected T throwRejected(Object task) { + throw new RejectedExecutionException("Task " + task + " rejected from " + this); + } + + protected T register(T task) { + tasks.add(task); + return task; + } + + protected void unregister(Task task) { + tasks.remove(task); + + if (!isAlive() && tasks.isEmpty()) + terminated.signalAll(); + } + + protected void shutdownTasks(Set tasks) { + // do nothing + } + + protected interface Task extends Runnable, Callable, Future { + + Future getFuture(); + + void setFuture(Future future); + + void acquireExecution(); + + void releaseExecution(); + + } + + protected class RunnableTask implements Task { + + private final Semaphore executionLock = new Semaphore(1); + + private final Runnable runnable; + private final boolean unregister; + + private Future future; + + public RunnableTask(Runnable runnable, boolean unregister) { + this.runnable = runnable; + this.unregister = unregister; + } + + @Override + public Future getFuture() { + return future; + } + + @Override + public void setFuture(Future future) { + this.future = future; + } + + @Override + public void acquireExecution() { + executionLock.acquireUninterruptibly(); + } + + @Override + public void releaseExecution() { + executionLock.release(); + } + + @Override + public void run() { + acquireExecution(); + try { + if (!unregister && isCancelled()) + return; + try { + runnable.run(); + } finally { + if (unregister) { + acquireAlive(); + try { + unregister(this); + } finally { + releaseAlive(); + } + } + } + } finally { + releaseExecution(); + } + } + + @Override + public V call() throws Exception { + run(); + return null; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return getFuture().cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return getFuture().isCancelled(); + } + + @Override + public boolean isDone() { + return getFuture().isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return getFuture().get(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return getFuture().get(timeout, unit); + } + } + + protected class CallableTask implements Task { + + private final Semaphore executionLock = new Semaphore(1); + + private final Callable callable; + private final boolean unregister; + + private Future future; + + public CallableTask(Callable callable, boolean unregister) { + this.callable = callable; + this.unregister = unregister; + } + + @Override + public Future getFuture() { + return future; + } + + @Override + public void setFuture(Future future) { + this.future = future; + } + + @Override + public void acquireExecution() { + executionLock.acquireUninterruptibly(); + } + + @Override + public void releaseExecution() { + executionLock.release(); + } + + @Override + public void run() { + acquireExecution(); + try { + if (!unregister && isCancelled()) + return; + try { + try { + callable.call(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } finally { + if (unregister) { + acquireAlive(); + try { + unregister(this); + } finally { + releaseAlive(); + } + } + } + } finally { + releaseExecution(); + } + } + + @Override + public V call() throws Exception { + acquireExecution(); + try { + if (!unregister && isCancelled()) + return null; + try { + return callable.call(); + } finally { + if (unregister) { + acquireAlive(); + try { + unregister(this); + } finally { + releaseAlive(); + } + } + } + } finally { + releaseExecution(); + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return getFuture().cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return getFuture().isCancelled(); + } + + @Override + public boolean isDone() { + return getFuture().isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return getFuture().get(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return getFuture().get(timeout, unit); + } + + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/executors/SubScheduledExecutorService.java b/core/src/main/java/com/orientechnologies/common/concur/executors/SubScheduledExecutorService.java new file mode 100644 index 00000000000..0f1e2166eaa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/executors/SubScheduledExecutorService.java @@ -0,0 +1,258 @@ +/* + * + * * Copyright 2010-2016 OrientDB LTD (http://orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ + +package com.orientechnologies.common.concur.executors; + +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.*; + +/** + * Scheduled version of {@link SubExecutorService}. Supports delegation to {@link ScheduledThreadPoolExecutor} only. + * + * @author Sergey Sitnikov + */ +@SuppressWarnings({ "unchecked", "NullableProblems" }) +public class SubScheduledExecutorService extends SubExecutorService implements ScheduledExecutorService { + + /** + * Constructs a new SubExecutorService for the given executor service. + * + * @param executorService the underlying executor service to submit tasks to + */ + public SubScheduledExecutorService(ScheduledThreadPoolExecutor executorService) { + super(executorService); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + acquireAlive(); + try { + if (isAlive()) { + final ScheduledTask wrapped = new ScheduledRunnableTask(command, false); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().schedule((Runnable) wrapped, delay, unit)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(command); + } finally { + releaseAlive(); + } + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + acquireAlive(); + try { + if (isAlive()) { + final ScheduledTask wrapped = new ScheduledCallableTask(callable, false); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().schedule((Callable) wrapped, delay, unit)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(callable); + } finally { + releaseAlive(); + } + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + acquireAlive(); + try { + if (isAlive()) { + final ScheduledTask wrapped = new ScheduledRunnableTask(command, true); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().scheduleAtFixedRate(wrapped, initialDelay, period, unit)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(command); + } finally { + releaseAlive(); + } + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + acquireAlive(); + try { + if (isAlive()) { + final ScheduledTask wrapped = new ScheduledRunnableTask(command, true); + wrapped.acquireExecution(); + try { + wrapped.setFuture(getExecutorService().scheduleWithFixedDelay(wrapped, initialDelay, delay, unit)); + return register(wrapped); + } finally { + wrapped.releaseExecution(); + } + } else + return throwRejected(command); + } finally { + releaseAlive(); + } + } + + @Override + protected ScheduledThreadPoolExecutor getExecutorService() { + return (ScheduledThreadPoolExecutor) super.getExecutorService(); + } + + @Override + protected void shutdownTasks(Set tasks) { + final ScheduledThreadPoolExecutor executorService = getExecutorService(); + final BlockingQueue queue = executorService.getQueue(); + final boolean abortPeriodic = !executorService.getContinueExistingPeriodicTasksAfterShutdownPolicy(); + final boolean abortDelayed = !executorService.getExecuteExistingDelayedTasksAfterShutdownPolicy(); + + for (Task task : new ArrayList(tasks)) + if (task instanceof ScheduledTask) { + final ScheduledTask scheduledTask = (ScheduledTask) task; + final boolean cancelled = task.isCancelled(); + + if (scheduledTask.isPeriodic()) { + if (abortPeriodic || cancelled) { + task.acquireExecution(); + try { + //noinspection SuspiciousMethodCalls + if (queue.remove(task.getFuture())) + try { + if (!cancelled) + task.cancel(false); + } finally { + unregister(task); + } + else { + if (!cancelled) + task.cancel(false); + unregister(task); // no try/finally, if the cancelation is failed an active task may be lost from the registry + } + } finally { + task.releaseExecution(); + } + } + } else if (abortDelayed || cancelled) { + //noinspection SuspiciousMethodCalls + if (queue.remove(task.getFuture())) + try { + if (!cancelled) + task.cancel(false); + } finally { + unregister(task); + } + } + } + + super.shutdownTasks(tasks); + } + + protected interface ScheduledTask extends Task, ScheduledFuture { + @Override + ScheduledFuture getFuture(); + + void setFuture(ScheduledFuture future); + + boolean isPeriodic(); + } + + protected class ScheduledRunnableTask extends RunnableTask implements ScheduledTask { + + private final boolean periodic; + + public ScheduledRunnableTask(Runnable runnable, boolean periodic) { + super(runnable, !periodic); + this.periodic = periodic; + } + + @Override + public ScheduledFuture getFuture() { + return (ScheduledFuture) super.getFuture(); + } + + @Override + public void setFuture(ScheduledFuture future) { + super.setFuture(future); + } + + @Override + public boolean isPeriodic() { + return periodic; + } + + @Override + public long getDelay(TimeUnit unit) { + return getFuture().getDelay(unit); + } + + @Override + public int compareTo(Delayed o) { + return getFuture().compareTo(o); + } + + } + + protected class ScheduledCallableTask extends CallableTask implements ScheduledTask { + + private final boolean periodic; + + public ScheduledCallableTask(Callable callable, boolean periodic) { + super(callable, !periodic); + this.periodic = periodic; + } + + @Override + public ScheduledFuture getFuture() { + return (ScheduledFuture) super.getFuture(); + } + + @Override + public void setFuture(ScheduledFuture future) { + super.setFuture(future); + } + + @Override + public boolean isPeriodic() { + return periodic; + } + + @Override + public long getDelay(TimeUnit unit) { + return getFuture().getDelay(unit); + } + + @Override + public int compareTo(Delayed o) { + return getFuture().compareTo(o); + } + + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OAbstractLock.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OAbstractLock.java new file mode 100644 index 00000000000..ab47d63a93c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OAbstractLock.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import java.util.concurrent.Callable; + +/** + * Abstract Lock class. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OAbstractLock implements OLock { + + @Override + public V callInLock(final Callable iCallback) throws Exception { + lock(); + try { + + return iCallback.call(); + + } finally { + unlock(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OAdaptiveLock.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OAdaptiveLock.java new file mode 100755 index 00000000000..78c7c9d819b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OAdaptiveLock.java @@ -0,0 +1,193 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.concur.OTimeoutException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Adaptive class to handle shared resources. It's configurable specifying if it's running in a concurrent environment and allow o + * specify a maximum timeout to avoid deadlocks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OAdaptiveLock extends OAbstractLock { + private final ReentrantLock lock = new ReentrantLock(); + private final boolean concurrent; + private final int timeout; + private final boolean ignoreThreadInterruption; + + public OAdaptiveLock() { + this.concurrent = true; + this.timeout = 0; + this.ignoreThreadInterruption = false; + } + + public OAdaptiveLock(final int iTimeout) { + this.concurrent = true; + this.timeout = iTimeout; + this.ignoreThreadInterruption = false; + } + + public OAdaptiveLock(final boolean iConcurrent) { + this.concurrent = iConcurrent; + this.timeout = 0; + this.ignoreThreadInterruption = false; + } + + public OAdaptiveLock(final boolean iConcurrent, final int iTimeout, boolean ignoreThreadInterruption) { + this.concurrent = iConcurrent; + this.timeout = iTimeout; + this.ignoreThreadInterruption = ignoreThreadInterruption; + } + + public void lock() { + if (concurrent) + if (timeout > 0) { + try { + if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) + // OK + return; + } catch (InterruptedException e) { + if (ignoreThreadInterruption) { + // IGNORE THE THREAD IS INTERRUPTED: TRY TO RE-LOCK AGAIN + try { + if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) { + // OK, RESET THE INTERRUPTED STATE + Thread.currentThread().interrupt(); + return; + } + } catch (InterruptedException e2) { + Thread.currentThread().interrupt(); + } + } + + throw OException.wrapException(new OLockException("Thread interrupted while waiting for resource of class '" + getClass() + + "' with timeout=" + timeout), e); + } + + throwTimeoutException(lock); + + } else + lock.lock(); + } + + public boolean tryAcquireLock() { + return tryAcquireLock(timeout, TimeUnit.MILLISECONDS); + } + + public boolean tryAcquireLock(final long iTimeout, final TimeUnit iUnit) { + if (concurrent) + if (timeout > 0) + try { + return lock.tryLock(iTimeout, iUnit); + } catch (InterruptedException e) { + throw OException.wrapException(new OLockException("Thread interrupted while waiting for resource of class '" + getClass() + + "' with timeout=" + timeout), e); + } + else + return lock.tryLock(); + + return true; + } + + public void unlock() { + if (concurrent) + lock.unlock(); + } + + @Override + public void close() { + try { + if (lock.isLocked()) + lock.unlock(); + } catch (Exception e) { + OLogManager.instance().debug(this, "Cannot unlock a lock", e); + } + } + + private void throwTimeoutException(Lock lock) { + final String owner = extractLockOwnerStackTrace(lock); + + throw new OTimeoutException("Timeout on acquiring exclusive lock against resource of class: " + getClass() + " with timeout=" + + timeout + (owner != null ? "\n" + owner : "")); + } + + private String extractLockOwnerStackTrace(Lock lock) { + try { + Field syncField = lock.getClass().getDeclaredField("sync"); + syncField.setAccessible(true); + + Object sync = syncField.get(lock); + Method getOwner = sync.getClass().getSuperclass().getDeclaredMethod("getOwner"); + getOwner.setAccessible(true); + + final Thread owner = (Thread) getOwner.invoke(sync); + if (owner == null) + return null; + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + printWriter.append("Owner thread : ").append(owner.toString()).append("\n"); + + StackTraceElement[] stackTrace = owner.getStackTrace(); + for (StackTraceElement traceElement : stackTrace) + printWriter.println("\tat " + traceElement); + + printWriter.flush(); + return stringWriter.toString(); + } catch (RuntimeException e) { + return null; + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + + } + + public boolean isConcurrent() { + return concurrent; + } + + public ReentrantLock getUnderlying() { + return lock; + } + + public boolean isHeldByCurrentThread() { + return lock.isHeldByCurrentThread(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OComparableLockManager.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OComparableLockManager.java new file mode 100644 index 00000000000..4900b80a0dc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OComparableLockManager.java @@ -0,0 +1,190 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.exception.OException; + +import java.util.Collection; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class OComparableLockManager { + public enum LOCK { + SHARED, EXCLUSIVE + } + + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + private long acquireTimeout; + protected final ConcurrentSkipListMap map; + private final boolean enabled; + private final static Object NULL_KEY = new Object(); + + @SuppressWarnings("serial") + private static class CountableLock { + private final AtomicInteger countLocks = new AtomicInteger(1); + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + } + + public OComparableLockManager(final boolean iEnabled, final int iAcquireTimeout) { + this(iEnabled, iAcquireTimeout, defaultConcurrency()); + } + + public OComparableLockManager(final boolean iEnabled, final int iAcquireTimeout, final int concurrencyLevel) { + final int cL = closestInteger(concurrencyLevel); + + map = new ConcurrentSkipListMap(); + + acquireTimeout = iAcquireTimeout; + enabled = iEnabled; + } + + public void acquireSharedLock(final T key) { + acquireLock(key, LOCK.SHARED); + } + + public void releaseSharedLock(final T key) { + releaseLock(Thread.currentThread(), key, LOCK.SHARED); + } + + public void acquireExclusiveLock(final T key) { + acquireLock(key, LOCK.EXCLUSIVE); + } + + public void releaseExclusiveLock(final T key) { + releaseLock(Thread.currentThread(), key, LOCK.EXCLUSIVE); + } + + public void acquireLock(final T iResourceId, final LOCK iLockType) { + acquireLock(iResourceId, iLockType, acquireTimeout); + } + + public void acquireLock(final T iResourceId, final LOCK iLockType, long iTimeout) { + if (!enabled) + return; + + if (!enabled) + return; + + T immutableResource = getImmutableResourceId(iResourceId); + if (immutableResource == null) + immutableResource = (T) NULL_KEY; + + CountableLock lock; + + + while (true) { + lock = new CountableLock(); + + CountableLock oldLock = map.putIfAbsent(immutableResource, lock); + if (oldLock == null) + break; + + lock = oldLock; + final int oldValue = lock.countLocks.get(); + + if (oldValue >= 0) { + if (lock.countLocks.compareAndSet(oldValue, oldValue + 1)) { + assert map.get(immutableResource) == lock; + break; + } + } else { + map.remove(immutableResource, lock); + } + } + + try { + if (iTimeout <= 0) { + if (iLockType == LOCK.SHARED) + lock.readWriteLock.readLock().lock(); + else + lock.readWriteLock.writeLock().lock(); + } else { + try { + if (iLockType == LOCK.SHARED) { + if (!lock.readWriteLock.readLock().tryLock(iTimeout, TimeUnit.MILLISECONDS)) + throw new OLockException( + "Timeout (" + iTimeout + "ms) on acquiring resource '" + iResourceId + "' because is locked from another thread"); + } else { + if (!lock.readWriteLock.writeLock().tryLock(iTimeout, TimeUnit.MILLISECONDS)) + throw new OLockException( + "Timeout (" + iTimeout + "ms) on acquiring resource '" + iResourceId + "' because is locked from another thread"); + } + } catch (InterruptedException e) { + throw OException + .wrapException(new OLockException("Thread interrupted while waiting for resource '" + iResourceId + "'"), e); + } + } + } catch (RuntimeException e) { + final int usages = lock.countLocks.decrementAndGet(); + if (usages == 0) + map.remove(immutableResource); + + throw e; + } + } + + public void releaseLock(final Object iRequester, T iResourceId, final LOCK iLockType) throws OLockException { + if (!enabled) + return; + + if (iResourceId == null) + iResourceId = (T) NULL_KEY; + + final CountableLock lock = map.get(iResourceId); + if (lock == null) + throw new OLockException( + "Error on releasing a non acquired lock by the requester '" + iRequester + "' against the resource: '" + iResourceId + + "'"); + + final int lockCount = lock.countLocks.decrementAndGet(); + if (lockCount == 0) { + if (lock.countLocks.compareAndSet(0, -1)) { + map.remove(iResourceId, lock); + } + } + + if (iLockType == LOCK.SHARED) + lock.readWriteLock.readLock().unlock(); + else + lock.readWriteLock.writeLock().unlock(); + } + + // For tests purposes. + public int getCountCurrentLocks() { + return map.size(); + } + + protected T getImmutableResourceId(final T iResourceId) { + return iResourceId; + } + + private static int defaultConcurrency() { + return (Runtime.getRuntime().availableProcessors() << 6) > DEFAULT_CONCURRENCY_LEVEL ? + (Runtime.getRuntime().availableProcessors() << 6) : + DEFAULT_CONCURRENCY_LEVEL; + } + + private static int closestInteger(int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/ODistributedCounter.java b/core/src/main/java/com/orientechnologies/common/concur/lock/ODistributedCounter.java new file mode 100755 index 00000000000..7f57ffb912e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/ODistributedCounter.java @@ -0,0 +1,152 @@ +package com.orientechnologies.common.concur.lock; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + */ +@SuppressFBWarnings(value = "VO_VOLATILE_REFERENCE_TO_ARRAY") +public class ODistributedCounter { + private static final int HASH_INCREMENT = 0x61c88647; + private static final int MAX_RETRIES = 8; + + private static final AtomicInteger nextHashCode = new AtomicInteger(); + private final AtomicBoolean isBusy = new AtomicBoolean(); + + private final int maxPartitions; + + private final ThreadLocal threadHashCode = new ThreadHashCode(); + private volatile AtomicLong[] counters; + + public ODistributedCounter() { + final AtomicLong[] cts = new AtomicLong[2]; + for (int i = 0; i < cts.length; i++) { + cts[i] = new AtomicLong(); + } + + counters = cts; + maxPartitions = Runtime.getRuntime().availableProcessors() << 3; + } + + public ODistributedCounter(int concurrencyLevel) { + final AtomicLong[] cts = new AtomicLong[2]; + for (int i = 0; i < cts.length; i++) { + cts[i] = new AtomicLong(); + } + + counters = cts; + maxPartitions = concurrencyLevel; + } + + public void increment() { + updateCounter(+1); + } + + public void decrement() { + updateCounter(-1); + } + + public void add(long delta) { + updateCounter(delta); + } + + public void clear() { + while (!isBusy.compareAndSet(false, true)) + ; + + final AtomicLong[] cts = new AtomicLong[counters.length]; + for (int i = 0; i < counters.length; i++) { + cts[i] = new AtomicLong(); + } + + counters = cts; + + isBusy.set(false); + } + + private void updateCounter(long delta) { + final int hashCode = threadHashCode.get(); + + while (true) { + final AtomicLong[] cts = counters; + final int index = (cts.length - 1) & hashCode; + + AtomicLong counter = cts[index]; + + if (counter == null) { + if (!isBusy.get() && isBusy.compareAndSet(false, true)) { + if (cts == counters) { + counter = cts[index]; + + if (counter == null) + cts[index] = new AtomicLong(); + } + + isBusy.set(false); + } + + continue; + } else { + long v = counter.get(); + int retries = 0; + + if (cts.length < maxPartitions) { + while (retries < MAX_RETRIES) { + if (!counter.compareAndSet(v, v + delta)) { + retries++; + v = counter.get(); + } else { + return; + } + } + } else { + counter.addAndGet(delta); + return; + } + + if (!isBusy.get() && isBusy.compareAndSet(false, true)) { + if (cts == counters) { + if (cts.length < maxPartitions) { + final AtomicLong[] newCts = new AtomicLong[cts.length << 1]; + System.arraycopy(cts, 0, newCts, 0, cts.length); + counters = newCts; + } + } + + isBusy.set(false); + } + + continue; + } + } + } + + public boolean isEmpty() { + return get() == 0; + } + + public long get() { + long sum = 0; + + for (AtomicLong counter : counters) + if (counter != null) + sum += counter.get(); + + return sum; + } + + private static int nextHashCode() { + return nextHashCode.getAndAdd(HASH_INCREMENT); + } + + private static final class ThreadHashCode extends ThreadLocal { + @Override + protected Integer initialValue() { + return nextHashCode(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OIndexOneEntryPerKeyLockManager.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OIndexOneEntryPerKeyLockManager.java new file mode 100755 index 00000000000..88181a33200 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OIndexOneEntryPerKeyLockManager.java @@ -0,0 +1,343 @@ +/* + * + * * Copyright 2010-2017 OrientDB LTD (http://orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.OCompositeKey; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Basically the same thing as {@link OOneEntryPerKeyLockManager}, but uses {@link ConcurrentHashMap} internally which has better + * memory footprint. + */ +public class OIndexOneEntryPerKeyLockManager implements OLockManager { + + private final static Object NULL_KEY = new Object(); + + private final ConcurrentHashMap locks; + + public OIndexOneEntryPerKeyLockManager() { + this(OGlobalConfiguration.ENVIRONMENT_LOCK_MANAGER_CONCURRENCY_LEVEL.getValueAsInteger()); + } + + public OIndexOneEntryPerKeyLockManager(int concurrencyLevel) { + final int ceilingConcurrencyLevel = ceilingPowerOf2(concurrencyLevel); + locks = new ConcurrentHashMap(16, 0.75F, ceilingConcurrencyLevel); + } + + @Override + public Lock acquireSharedLock(T key) { + return acquireLock(key, true); + } + + @Override + public void releaseSharedLock(T key) { + releaseLock(key, true); + } + + @Override + public Lock acquireExclusiveLock(T key) { + return acquireLock(key, false); + } + + @Override + public void releaseExclusiveLock(T key) { + releaseLock(key, false); + } + + @Override + public Lock[] acquireSharedLocksInBatch(T... keys) { + return acquireLockInBatch(keys, true); + } + + @Override + public Lock[] acquireExclusiveLocksInBatch(T... keys) { + return acquireLockInBatch(keys, false); + } + + @Override + public Lock[] acquireExclusiveLocksInBatch(Collection keys) { + if (keys == null || keys.isEmpty()) + return new Lock[0]; + + final List comparables = new ArrayList(); + + int seenNulls = 0; + for (T key : keys) { + if (key instanceof Comparable) { + comparables.add((Comparable) key); + } else if (key == null) { + ++seenNulls; + } else { + throw new IllegalArgumentException( + "In order to lock a key in a batch it should implement " + Comparable.class.getName() + " interface"); + } + } + + //noinspection unchecked + Collections.sort(comparables); + + final Lock[] locks = new Lock[comparables.size() + seenNulls]; + int i = 0; + for (int j = 0; j < seenNulls; ++j) + //noinspection unchecked + locks[i++] = acquireExclusiveLock((T) NULL_KEY); + for (Comparable key : comparables) + //noinspection unchecked + locks[i++] = acquireExclusiveLock((T) key); + + return locks; + } + + @Override + public void lockAllExclusive() { + for (CountableLock lock : locks.values()) { + lock.readWriteLock.writeLock().lock(); + } + } + + @Override + public void unlockAllExclusive() { + for (CountableLock lock : locks.values()) { + lock.readWriteLock.writeLock().unlock(); + } + } + + private Lock acquireLock(T key, boolean read) { + key = immutalizeKey(key); + + if (key == null) + //noinspection unchecked + key = (T) NULL_KEY; + + CountableLock lock; + do { + lock = locks.get(key); + + if (lock != null) { + final int oldLevel = lock.level.get(); + + if (oldLevel >= 0) { + if (lock.level.compareAndSet(oldLevel, oldLevel + 1)) + break; + } else { + locks.remove(key, lock); + } + } + } while (lock != null); + + if (lock == null) { + while (true) { + lock = new CountableLock(); + + CountableLock oldLock = locks.putIfAbsent(key, lock); + if (oldLock == null) + break; + + lock = oldLock; + final int oldLevel = lock.level.get(); + + if (oldLevel >= 0) { + if (lock.level.compareAndSet(oldLevel, oldLevel + 1)) { + assert locks.get(key) == lock; + break; + } + } else { + locks.remove(key, lock); + } + } + } + + if (read) + lock.readWriteLock.readLock().lock(); + else + lock.readWriteLock.writeLock().lock(); + + return new CountableLockWrapper(key, lock, locks, read); + } + + private void releaseLock(T key, boolean read) throws OLockException { + key = immutalizeKey(key); + + if (key == null) + //noinspection unchecked + key = (T) NULL_KEY; + + final CountableLock lock = locks.get(key); + if (lock == null) + throw new OLockException( + "Error on releasing a non acquired lock by thread '" + Thread.currentThread() + "' against the resource: '" + key + "'"); + + if (lock.level.decrementAndGet() == 0 && lock.level.compareAndSet(0, -1)) { + assert lock.level.get() == -1; + locks.remove(key, lock); + } + + if (read) + lock.readWriteLock.readLock().unlock(); + else + lock.readWriteLock.writeLock().unlock(); + } + + private Lock[] acquireLockInBatch(T[] keys, boolean read) { + if (keys == null || keys.length == 0) + return null; + + final List comparables = new ArrayList(); + + int seenNulls = 0; + for (T key : keys) { + if (key instanceof Comparable) { + comparables.add((Comparable) key); + } else if (key == null) { + ++seenNulls; + } else { + throw new IllegalArgumentException( + "In order to lock a key in a batch it should implement " + Comparable.class.getName() + " interface"); + } + } + + //noinspection unchecked + Collections.sort(comparables); + + final Lock[] locks = new Lock[comparables.size() + seenNulls]; + int i = 0; + for (int j = 0; j < seenNulls; ++j) + //noinspection unchecked + locks[i++] = read ? acquireSharedLock((T) NULL_KEY) : acquireExclusiveLock((T) NULL_KEY); + for (Comparable key : comparables) + //noinspection unchecked + locks[i++] = read ? acquireSharedLock((T) key) : acquireExclusiveLock((T) key); + + return locks; + } + + private T immutalizeKey(T key) { + if (key instanceof OIdentifiable) { + //noinspection unchecked + return (T) ((OIdentifiable) key).getIdentity().copy(); + } else if (key instanceof OCompositeKey) { + final OCompositeKey compositeKey = (OCompositeKey) key; + + boolean needsCopy = false; + for (Object subkey : compositeKey.getKeys()) { + assert !(subkey instanceof OCompositeKey); + + if (subkey instanceof OIdentifiable) { + needsCopy = true; + break; + } + } + + if (needsCopy) { + final OCompositeKey copy = new OCompositeKey(); + for (Object subkey : compositeKey.getKeys()) + copy.addKey(subkey instanceof OIdentifiable ? ((OIdentifiable) subkey).getIdentity().copy() : subkey); + + //noinspection unchecked + return (T) copy; + } else + return key; + } else + return key; + } + + public int getLockCount() { // for testing purposes only + return locks.size(); + } + + private static int ceilingPowerOf2(int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + } + + private static class CountableLock { + public final AtomicInteger level = new AtomicInteger(1); + public final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + } + + @SuppressWarnings("NullableProblems") + private static class CountableLockWrapper implements Lock { + + private final T key; + private final CountableLock lock; + private final ConcurrentHashMap locks; + private final boolean read; + + public CountableLockWrapper(T key, CountableLock lock, ConcurrentHashMap locks, boolean read) { + this.key = key; + this.lock = lock; + this.locks = locks; + this.read = read; + } + + @Override + public void lock() { + throw new UnsupportedOperationException(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public void unlock() { + assert lock == locks.get(key); + + if (lock.level.decrementAndGet() == 0 && lock.level.compareAndSet(0, -1)) { + assert lock.level.get() == -1; + locks.remove(key, lock); + } + + if (read) + lock.readWriteLock.readLock().unlock(); + else + lock.readWriteLock.writeLock().unlock(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OInterruptedException.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OInterruptedException.java new file mode 100755 index 00000000000..0bcb4bdf698 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OInterruptedException.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.exception.OSystemException; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 3/6/14 + */ +public class OInterruptedException extends OSystemException { + + public OInterruptedException(OInterruptedException exception) { + super(exception); + } + + public OInterruptedException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OLock.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OLock.java new file mode 100644 index 00000000000..6b60d9a06ee --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OLock.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import java.util.concurrent.Callable; + +/** + * Interface for locks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OLock { + public void lock(); + + public void unlock(); + + public V callInLock(Callable iCallback) throws Exception; + + public void close(); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OLockException.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OLockException.java new file mode 100755 index 00000000000..1d9aba2c9b0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OLockException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.exception.OSystemException; + +public class OLockException extends OSystemException { + private static final long serialVersionUID = 2215169397325875189L; + + public OLockException(OLockException exception) { + super(exception); + } + + public OLockException(String iMessage) { + super(iMessage); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OLockManager.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OLockManager.java new file mode 100644 index 00000000000..15ee057d3b8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OLockManager.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ +package com.orientechnologies.common.concur.lock; + +import java.util.Collection; +import java.util.concurrent.locks.Lock; + +/** + * Lock Manager interface. + * + * @author Luca Garulli + * @since 2.2.0 + */ +public interface OLockManager { + Lock acquireSharedLock(T key); + + void releaseSharedLock(T key); + + Lock acquireExclusiveLock(T key); + + void releaseExclusiveLock(T key); + + Lock[] acquireExclusiveLocksInBatch(T... values); + + Lock[] acquireExclusiveLocksInBatch(Collection values); + + Lock[] acquireSharedLocksInBatch(T[] keys); + + void lockAllExclusive(); + + void unlockAllExclusive(); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OModificationOperationProhibitedException.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OModificationOperationProhibitedException.java new file mode 100755 index 00000000000..ae74f031389 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OModificationOperationProhibitedException.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.exception.OCoreException; + +/** + * Exception is thrown in case DB is locked for modifications but modification request ist trying to be acquired. + * + * @author Andrey Lomakin + * @since 03.07.12 + */ +public class OModificationOperationProhibitedException extends OCoreException implements OHighLevelException { + private static final long serialVersionUID = 1L; + + public OModificationOperationProhibitedException(OModificationOperationProhibitedException exception) { + super(exception); + } + + public OModificationOperationProhibitedException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OOneEntryPerKeyLockManager.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OOneEntryPerKeyLockManager.java new file mode 100755 index 00000000000..f13cc8ac74c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OOneEntryPerKeyLockManager.java @@ -0,0 +1,367 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.lock; + +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Original Lock Manager implementation that uses a concurrent linked hash map to store one entry per key. This could be very + * expensive in case the number of locks are a lot. This implementation works better than {@link OPartitionedLockManager} when + * running distributed because there is no way to cause a deadlock based on different keys. + * + * @param + * Type of keys + * + * @author Luca Garulli + */ +public class OOneEntryPerKeyLockManager implements OLockManager { + public enum LOCK { + SHARED, EXCLUSIVE + } + + private long acquireTimeout; + protected final ConcurrentLinkedHashMap map; + private final boolean enabled; + private final int amountOfCachedInstances; + + private final static Object NULL_KEY = new Object(); + + @SuppressWarnings("serial") + private static class CountableLock { + private final AtomicInteger countLocks = new AtomicInteger(1); + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + } + + public OOneEntryPerKeyLockManager(final boolean iEnabled, final int iAcquireTimeout, final int amountOfCachedInstances) { + this(iEnabled, iAcquireTimeout, OGlobalConfiguration.ENVIRONMENT_LOCK_MANAGER_CONCURRENCY_LEVEL.getValueAsInteger(), + amountOfCachedInstances); + } + + public OOneEntryPerKeyLockManager(final boolean iEnabled, final int iAcquireTimeout, final int concurrencyLevel, + final int amountOfCachedInstances) { + + this.amountOfCachedInstances = amountOfCachedInstances; + final int cL = closestInteger(concurrencyLevel); + + map = new ConcurrentLinkedHashMap.Builder().concurrencyLevel(cL).maximumWeightedCapacity(Long.MAX_VALUE) + .build(); + + acquireTimeout = iAcquireTimeout; + enabled = iEnabled; + } + + @Override + public Lock acquireSharedLock(final T key) { + return acquireLock(key, LOCK.SHARED); + } + + @Override + public void releaseSharedLock(final T key) { + releaseLock(Thread.currentThread(), key, LOCK.SHARED); + } + + @Override + public Lock acquireExclusiveLock(final T key) { + return acquireLock(key, LOCK.EXCLUSIVE); + } + + @Override + public void releaseExclusiveLock(final T key) { + releaseLock(Thread.currentThread(), key, LOCK.EXCLUSIVE); + } + + public Lock acquireLock(final T iResourceId, final LOCK iLockType) { + return acquireLock(iResourceId, iLockType, acquireTimeout); + } + + public Lock acquireLock(final T iResourceId, final LOCK iLockType, long iTimeout) { + if (!enabled) + return null; + + T immutableResource = getImmutableResourceId(iResourceId); + if (immutableResource == null) + immutableResource = (T) NULL_KEY; + + CountableLock lock; + do { + lock = map.get(immutableResource); + + if (lock != null) { + final int oldLockCount = lock.countLocks.get(); + + if (oldLockCount >= 0) { + if (lock.countLocks.compareAndSet(oldLockCount, oldLockCount + 1)) { + break; + } + } else { + map.remove(immutableResource, lock); + } + } + } while (lock != null); + + if (lock == null) { + while (true) { + lock = new CountableLock(); + + CountableLock oldLock = map.putIfAbsent(immutableResource, lock); + if (oldLock == null) + break; + + lock = oldLock; + final int oldValue = lock.countLocks.get(); + + if (oldValue >= 0) { + if (lock.countLocks.compareAndSet(oldValue, oldValue + 1)) { + assert map.get(immutableResource) == lock; + break; + } + } else { + map.remove(immutableResource, lock); + } + } + } + + if (map.size() > amountOfCachedInstances) { + final Iterator keyToRemoveIterator = map.ascendingKeySetWithLimit(1).iterator(); + if (keyToRemoveIterator.hasNext()) { + final T keyToRemove = keyToRemoveIterator.next(); + final CountableLock lockToRemove = map.get(keyToRemove); + if (lockToRemove != null) { + final int counter = lockToRemove.countLocks.get(); + if (counter == 0 && lockToRemove.countLocks.compareAndSet(counter, -1)) { + assert lockToRemove.countLocks.get() == -1; + map.remove(keyToRemove, lockToRemove); + } + } + } + } + + try { + if (iTimeout <= 0) { + if (iLockType == LOCK.SHARED) + lock.readWriteLock.readLock().lock(); + else + lock.readWriteLock.writeLock().lock(); + } else { + try { + if (iLockType == LOCK.SHARED) { + if (!lock.readWriteLock.readLock().tryLock(iTimeout, TimeUnit.MILLISECONDS)) + throw new OLockException( + "Timeout (" + iTimeout + "ms) on acquiring resource '" + iResourceId + "' because is locked from another thread"); + } else { + if (!lock.readWriteLock.writeLock().tryLock(iTimeout, TimeUnit.MILLISECONDS)) + throw new OLockException( + "Timeout (" + iTimeout + "ms) on acquiring resource '" + iResourceId + "' because is locked from another thread"); + } + } catch (InterruptedException e) { + throw OException.wrapException(new OLockException("Thread interrupted while waiting for resource '" + iResourceId + "'"), + e); + } + } + + return new CountableLockWrapper(lock, iLockType == LOCK.SHARED); + } catch (RuntimeException e) { + final int usages = lock.countLocks.decrementAndGet(); + if (usages == 0) + map.remove(immutableResource); + + throw e; + } + } + + public void releaseLock(final Object iRequester, T iResourceId, final LOCK iLockType) throws OLockException { + if (!enabled) + return; + + if (iResourceId == null) + iResourceId = (T) NULL_KEY; + + final CountableLock lock = map.get(iResourceId); + if (lock == null) + throw new OLockException("Error on releasing a non acquired lock by the requester '" + iRequester + + "' against the resource: '" + iResourceId + "'"); + + lock.countLocks.decrementAndGet(); + + if (iLockType == LOCK.SHARED) + lock.readWriteLock.readLock().unlock(); + else + lock.readWriteLock.writeLock().unlock(); + } + + @Override + public Lock[] acquireExclusiveLocksInBatch(final T... values) { + return acquireLockInBatch(values, true); + } + + @Override + public Lock[] acquireSharedLocksInBatch(final T... values) { + return acquireLockInBatch(values, false); + } + + @Override + public Lock[] acquireExclusiveLocksInBatch(Collection values) { + if (values == null || values.isEmpty()) + return new Lock[0]; + + final List comparables = new ArrayList(); + + int seenNulls = 0; + for (T value : values) { + if (value instanceof Comparable) { + comparables.add((Comparable) value); + } else if (value == null) { + ++seenNulls; + } else { + throw new IllegalArgumentException( + "In order to lock value in batch it should implement " + Comparable.class.getName() + " interface"); + } + } + + Collections.sort(comparables); + + final Lock[] locks = new Lock[comparables.size() + seenNulls]; + int i = 0; + for (int j = 0; j < seenNulls; ++j) + locks[i++] = acquireExclusiveLock((T) NULL_KEY); + for (Comparable value : comparables) + locks[i++] = acquireExclusiveLock((T) value); + + return locks; + } + + @Override + public void lockAllExclusive() { + for (CountableLock lock : map.values()) { + lock.readWriteLock.writeLock().lock(); + } + } + + @Override + public void unlockAllExclusive() { + for (CountableLock lock : map.values()) { + lock.readWriteLock.writeLock().unlock(); + } + } + + // For tests purposes. + public int getCountCurrentLocks() { + return map.size(); + } + + protected T getImmutableResourceId(final T iResourceId) { + return iResourceId; + } + + private static int closestInteger(int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + } + + @SuppressWarnings("NullableProblems") + /* internal */ static class CountableLockWrapper implements Lock { + + private final CountableLock countableLock; + private final boolean read; + + public CountableLockWrapper(CountableLock countableLock, boolean read) { + this.countableLock = countableLock; + this.read = read; + } + + /* internal */ int getLockCount() { // for testing purposes + return countableLock.countLocks.get(); + } + + @Override + public void lock() { + throw new UnsupportedOperationException(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public void unlock() { + countableLock.countLocks.decrementAndGet(); + + if (read) + countableLock.readWriteLock.readLock().unlock(); + else + countableLock.readWriteLock.writeLock().unlock(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + + protected Lock[] acquireLockInBatch(T[] values, final boolean exclusiveMode) { + if (values == null || values.length == 0) + return null; + + final List comparables = new ArrayList(); + + int seenNulls = 0; + for (T value : values) { + if (value instanceof Comparable) { + comparables.add((Comparable) value); + } else if (value == null) { + ++seenNulls; + } else { + throw new IllegalArgumentException( + "In order to lock value in batch it should implement " + Comparable.class.getName() + " interface"); + } + } + + Collections.sort(comparables); + + final Lock[] locks = new Lock[comparables.size() + seenNulls]; + int i = 0; + for (int j = 0; j < seenNulls; ++j) + locks[i++] = exclusiveMode ? acquireExclusiveLock((T) NULL_KEY) : acquireSharedLock((T) NULL_KEY); + for (Comparable value : comparables) + locks[i++] = exclusiveMode ? acquireExclusiveLock((T) value) : acquireSharedLock((T) value); + + return locks; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OPartitionedLockManager.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OPartitionedLockManager.java new file mode 100755 index 00000000000..c8a3e325a0d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OPartitionedLockManager.java @@ -0,0 +1,498 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Lock manager implementation that uses multipel partitions to increase the level of concurrency without having to keep one entry + * per locked key, like for {@link OOneEntryPerKeyLockManager} implementation. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 8/11/14 + */ +public class OPartitionedLockManager implements OLockManager { + private static final int HASH_BITS = 0x7fffffff; + + private final int concurrencyLevel = closestInteger( + OGlobalConfiguration.ENVIRONMENT_LOCK_MANAGER_CONCURRENCY_LEVEL.getValueAsInteger()); + private final int mask = concurrencyLevel - 1; + + private final ReadWriteLock[] locks; + private final OReadersWriterSpinLock[] spinLocks; + + private final boolean useSpinLock; + private final Comparator comparator = new Comparator() { + @Override + public int compare(final Object one, final Object two) { + final int indexOne; + if (one == null) + indexOne = 0; + else + indexOne = index(one.hashCode()); + + final int indexTwo; + if (two == null) + indexTwo = 0; + else + indexTwo = index(two.hashCode()); + + if (indexOne > indexTwo) + return 1; + + if (indexOne < indexTwo) + return -1; + + return 0; + } + }; + + private static final class SpinLockWrapper implements Lock { + private final boolean readLock; + private final OReadersWriterSpinLock spinLock; + + private SpinLockWrapper(boolean readLock, OReadersWriterSpinLock spinLock) { + this.readLock = readLock; + this.spinLock = spinLock; + } + + @Override + public void lock() { + throw new UnsupportedOperationException(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public void unlock() { + if (readLock) + spinLock.releaseReadLock(); + else + spinLock.releaseWriteLock(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + + public OPartitionedLockManager() { + this(false); + } + + public OPartitionedLockManager(boolean useSpinLock) { + this.useSpinLock = useSpinLock; + + if (useSpinLock) { + OReadersWriterSpinLock[] lcks = new OReadersWriterSpinLock[concurrencyLevel]; + + for (int i = 0; i < lcks.length; i++) + lcks[i] = new OReadersWriterSpinLock(concurrencyLevel); + + spinLocks = lcks; + locks = null; + } else { + ReadWriteLock[] lcks = new ReadWriteLock[concurrencyLevel]; + for (int i = 0; i < lcks.length; i++) + lcks[i] = new ReentrantReadWriteLock(); + + locks = lcks; + spinLocks = null; + } + } + + private static int closestInteger(int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + } + + private static int longHashCode(long value) { + return (int) (value ^ (value >>> 32)); + } + + private int index(int hashCode) { + return shuffleHashCode(hashCode) & mask; + } + + public static int shuffleHashCode(int h) { + return (h ^ (h >>> 16)) & HASH_BITS; + } + + public Lock acquireExclusiveLock(long value) { + final int hashCode = longHashCode(value); + final int index = index(hashCode); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireWriteLock(); + return new SpinLockWrapper(false, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + lock.lock(); + + return lock; + } + + public Lock acquireExclusiveLock(int value) { + final int index = index(value); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireWriteLock(); + + return new SpinLockWrapper(false, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + lock.lock(); + return lock; + } + + @Override + public Lock acquireExclusiveLock(T value) { + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireWriteLock(); + + return new SpinLockWrapper(false, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + + lock.lock(); + return lock; + } + + public void lockAllExclusive() { + if (useSpinLock) { + for (OReadersWriterSpinLock spinLock : spinLocks) { + spinLock.acquireWriteLock(); + } + } else { + for (ReadWriteLock readWriteLock : locks) { + readWriteLock.writeLock().lock(); + } + } + } + + public void unlockAllExclusive() { + if (useSpinLock) { + for (OReadersWriterSpinLock spinLock : spinLocks) { + spinLock.releaseWriteLock(); + } + } else { + for (ReadWriteLock readWriteLock : locks) { + readWriteLock.writeLock().unlock(); + } + } + } + + public boolean tryAcquireExclusiveLock(final T value, final long timeout) throws InterruptedException { + if (useSpinLock) + throw new IllegalStateException("Spin lock does not support try lock mode"); + + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + return lock.tryLock(timeout, TimeUnit.MILLISECONDS); + } + + @Override + public Lock[] acquireExclusiveLocksInBatch(final T... value) { + if (value == null) + return new Lock[0]; + + final Lock[] locks = new Lock[value.length]; + final T[] sortedValues = getOrderedValues(value); + + for (int n = 0; n < sortedValues.length; n++) { + locks[n] = acquireExclusiveLock(sortedValues[n]); + } + + return locks; + } + + public Lock[] acquireSharedLocksInBatch(final T... value) { + if (value == null) + return new Lock[0]; + + final Lock[] locks = new Lock[value.length]; + final T[] sortedValues = getOrderedValues(value); + + for (int i = 0; i < sortedValues.length; i++) { + locks[i] = acquireSharedLock(sortedValues[i]); + } + + return locks; + } + + public Lock[] acquireExclusiveLocksInBatch(Collection values) { + if (values == null || values.isEmpty()) + return new Lock[0]; + + final Collection valCopy = getOrderedValues(values); + + final Lock[] locks = new Lock[values.size()]; + int i = 0; + for (T val : valCopy) { + locks[i++] = acquireExclusiveLock(val); + } + return locks; + } + + public Lock acquireSharedLock(long value) { + final int hashCode = longHashCode(value); + final int index = index(hashCode); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireReadLock(); + + return new SpinLockWrapper(true, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + lock.lock(); + + return lock; + + } + + public boolean tryAcquireSharedLock(T value, long timeout) throws InterruptedException { + if (useSpinLock) + throw new IllegalStateException("Spin lock does not support try lock mode"); + + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + return lock.tryLock(timeout, TimeUnit.MILLISECONDS); + } + + public Lock acquireSharedLock(int value) { + final int index = index(value); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireReadLock(); + + return new SpinLockWrapper(true, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + lock.lock(); + return lock; + } + + @Override + public Lock acquireSharedLock(final T value) { + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.acquireReadLock(); + + return new SpinLockWrapper(true, spinLock); + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + lock.lock(); + return lock; + } + + public void releaseSharedLock(final int value) { + final int index = index(value); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseReadLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + rwLock.readLock().unlock(); + } + + public void releaseSharedLock(final long value) { + final int hashCode = longHashCode(value); + final int index = index(hashCode); + + if (useSpinLock) { + final OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseReadLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + lock.unlock(); + } + + public void releaseSharedLock(final T value) { + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseReadLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.readLock(); + lock.unlock(); + } + + public void releaseExclusiveLock(final int value) { + final int index = index(value); + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseWriteLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + rwLock.writeLock().unlock(); + } + + public void releaseExclusiveLock(final long value) { + final int hashCode = longHashCode(value); + final int index = index(hashCode); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseWriteLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + lock.unlock(); + } + + public void releaseExclusiveLock(final T value) { + final int index; + if (value == null) + index = 0; + else + index = index(value.hashCode()); + + if (useSpinLock) { + OReadersWriterSpinLock spinLock = spinLocks[index]; + spinLock.releaseWriteLock(); + return; + } + + final ReadWriteLock rwLock = locks[index]; + + final Lock lock = rwLock.writeLock(); + lock.unlock(); + } + + public void releaseLock(final Lock lock) { + lock.unlock(); + } + + private T[] getOrderedValues(final T[] values) { + if (values.length < 2) { + // OPTIMIZED VERSION WITH JUST 1 ITEM (THE MOST COMMON) + return values; + } + + final T[] copy = Arrays.copyOf(values, values.length); + + Arrays.sort(copy, 0, copy.length, comparator); + + return copy; + } + + private Collection getOrderedValues(final Collection values) { + if (values.size() < 2) { + // OPTIMIZED VERSION WITH JUST 1 ITEM (THE MOST COMMON) + return values; + } + + final List valCopy = new ArrayList(values); + Collections.sort(valCopy, comparator); + + return valCopy; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/lock/OReadersWriterSpinLock.java b/core/src/main/java/com/orientechnologies/common/concur/lock/OReadersWriterSpinLock.java new file mode 100755 index 00000000000..b9f46665105 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/lock/OReadersWriterSpinLock.java @@ -0,0 +1,208 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.concur.lock; + +import com.orientechnologies.common.types.OModifiableInteger; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.AbstractOwnableSynchronizer; +import java.util.concurrent.locks.LockSupport; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 8/18/14 + */ +@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") +public class OReadersWriterSpinLock extends AbstractOwnableSynchronizer { + private static final long serialVersionUID = 7975120282194559960L; + + private final transient ODistributedCounter distributedCounter; + private final transient AtomicReference tail = new AtomicReference(); + private final transient ThreadLocal lockHolds = new InitOModifiableInteger(); + + private final transient ThreadLocal myNode = new InitWNode(); + private final transient ThreadLocal predNode = new ThreadLocal(); + + public OReadersWriterSpinLock() { + final WNode wNode = new WNode(); + wNode.locked = false; + + tail.set(wNode); + + distributedCounter = new ODistributedCounter(); + } + + public OReadersWriterSpinLock(int concurrencyLevel) { + final WNode wNode = new WNode(); + wNode.locked = false; + + tail.set(wNode); + distributedCounter = new ODistributedCounter(concurrencyLevel); + } + + public void acquireReadLock() { + final OModifiableInteger lHolds = lockHolds.get(); + + final int holds = lHolds.intValue(); + if (holds > 0) { + // we have already acquire read lock + lHolds.increment(); + return; + } else if (holds < 0) { + // write lock is acquired before, do nothing + return; + } + + distributedCounter.increment(); + + WNode wNode = tail.get(); + while (wNode.locked) { + distributedCounter.decrement(); + + while (wNode.locked && wNode == tail.get()) { + wNode.waitingReaders.add(Thread.currentThread()); + + if (wNode.locked && wNode == tail.get()) + LockSupport.park(this); + + wNode = tail.get(); + } + + distributedCounter.increment(); + + wNode = tail.get(); + } + + lHolds.increment(); + assert lHolds.intValue() == 1; + } + + public void releaseReadLock() { + final OModifiableInteger lHolds = lockHolds.get(); + final int holds = lHolds.intValue(); + if (holds > 1) { + lHolds.decrement(); + return; + } else if (holds < 0) { + // write lock was acquired before, do nothing + return; + } + + distributedCounter.decrement(); + + lHolds.decrement(); + assert lHolds.intValue() == 0; + } + + public void acquireWriteLock() { + final OModifiableInteger lHolds = lockHolds.get(); + + if (lHolds.intValue() < 0) { + lHolds.decrement(); + return; + } + + final WNode node = myNode.get(); + node.locked = true; + + final WNode pNode = tail.getAndSet(myNode.get()); + predNode.set(pNode); + + while (pNode.locked) { + pNode.waitingWriter = Thread.currentThread(); + + if (pNode.locked) + LockSupport.park(this); + } + + pNode.waitingWriter = null; + + final long beginTime = System.currentTimeMillis(); + while (!distributedCounter.isEmpty()) { + // IN THE WORST CASE CPU CAN BE 100% FOR MAXIMUM 1 SECOND + if (System.currentTimeMillis() - beginTime > 1000) + try { + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + setExclusiveOwnerThread(Thread.currentThread()); + + lHolds.decrement(); + assert lHolds.intValue() == -1; + } + + public void releaseWriteLock() { + final OModifiableInteger lHolds = lockHolds.get(); + + if (lHolds.intValue() < -1) { + lHolds.increment(); + return; + } + + setExclusiveOwnerThread(null); + + final WNode node = myNode.get(); + node.locked = false; + + final Thread waitingWriter = node.waitingWriter; + if (waitingWriter != null) + LockSupport.unpark(waitingWriter); + + Thread waitingReader; + while ((waitingReader = node.waitingReaders.poll()) != null) { + LockSupport.unpark(waitingReader); + } + + myNode.set(predNode.get()); + predNode.set(null); + + lHolds.increment(); + assert lHolds.intValue() == 0; + } + + private static final class InitWNode extends ThreadLocal { + @Override + protected WNode initialValue() { + return new WNode(); + } + } + + private static final class InitOModifiableInteger extends ThreadLocal { + @Override + protected OModifiableInteger initialValue() { + return new OModifiableInteger(); + } + } + + private final static class WNode { + private final Queue waitingReaders = new ConcurrentLinkedQueue(); + + private volatile boolean locked = true; + private volatile Thread waitingWriter; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OCloseable.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OCloseable.java new file mode 100755 index 00000000000..863e8ae3293 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OCloseable.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +public interface OCloseable { + /** + * Closes resources inside of call of OStorage#close(). So do not use locks when you call this method or you may have deadlock + * during storage close. This method is completely house keeping method and plays role of Object#finalize() in case of you need to + * clean up resources after storage is closed. + */ + void close(); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPool.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPool.java new file mode 100755 index 00000000000..cc2719c473b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPool.java @@ -0,0 +1,249 @@ +package com.orientechnologies.common.concur.resource; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This is internal API, do not use it. + * + * @author Andrey Lomakin Andrey Lomakin + * @since 15/12/14 + */ +@SuppressFBWarnings(value = "VO_VOLATILE_REFERENCE_TO_ARRAY") +public class OPartitionedObjectPool extends OOrientListenerAbstract { + private static final int HASH_INCREMENT = 0x61c88647; + private static final int MIN_POOL_SIZE = 2; + private static final AtomicInteger nextHashCode = new AtomicInteger(); + + private final int maxPartitions; + private final ObjectFactory factory; + private final int maxSize; + private volatile ThreadLocal threadHashCode = new ThreadHashCodeThreadLocal(); + + private final AtomicBoolean poolBusy = new AtomicBoolean(); + private volatile PoolPartition[] partitions; + private volatile boolean closed = false; + + public OPartitionedObjectPool(final ObjectFactory factory, final int maxSize, final int maxPartitions) { + this.factory = factory; + this.maxSize = maxSize; + this.maxPartitions = maxPartitions; + + final PoolPartition[] pts = new PoolPartition[maxPartitions < 2 ? maxPartitions : 2]; + + for (int i = 0; i < pts.length; i++) { + final PoolPartition partition = new PoolPartition(); + pts[i] = partition; + + initQueue(partition); + } + + partitions = pts; + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + public PoolEntry acquire() { + checkForClose(); + + final int th = threadHashCode.get(); + while (true) { + final PoolPartition[] pts = partitions; + + final int index = (pts.length - 1) & th; + + PoolPartition partition = pts[index]; + if (partition == null) { + if (!poolBusy.get() && poolBusy.compareAndSet(false, true)) { + if (pts == partitions) { + partition = pts[index]; + + if (partition == null) { + partition = new PoolPartition(); + initQueue(partition); + pts[index] = partition; + } + } + + poolBusy.set(false); + } + + continue; + } else { + T object = partition.queue.poll(); + if (object == null) { + if (pts.length < maxPartitions) { + if (!poolBusy.get() && poolBusy.compareAndSet(false, true)) { + if (pts == partitions) { + final PoolPartition[] newPartitions = new PoolPartition[pts.length << 1]; + System.arraycopy(pts, 0, newPartitions, 0, pts.length); + + partitions = newPartitions; + } + + poolBusy.set(false); + } + + continue; + } else { + if (partition.currentSize.get() >= maxSize) + throw new IllegalStateException("You have reached maximum pool size for given partition"); + + object = factory.create(); + + partition.acquiredObjects.incrementAndGet(); + partition.currentSize.incrementAndGet(); + + return new PoolEntry(partition, object); + } + } else { + if (!factory.isValid(object)) { + factory.close(object); + partition.currentSize.decrementAndGet(); + continue; + } + + factory.init(object); + partition.acquiredObjects.incrementAndGet(); + + return new PoolEntry(partition, object); + } + } + } + } + + public void release(PoolEntry entry) { + final PoolPartition partition = entry.partition; + partition.queue.offer(entry.object); + partition.acquiredObjects.decrementAndGet(); + } + + public int getMaxSize() { + return maxSize; + } + + public void close() { + if (closed) + return; + + closed = true; + + for (PoolPartition partition : partitions) { + if (partition == null) + continue; + + final Queue queue = partition.queue; + + while (!queue.isEmpty()) { + final T object = queue.poll(); + factory.close(object); + } + + } + + threadHashCode = null; + partitions = null; + } + + @Override + public void onShutdown() { + close(); + } + + @Override + public void onStartup() { + if (threadHashCode == null) + threadHashCode = new ThreadHashCodeThreadLocal(); + } + + public int getAvailableObjects() { + checkForClose(); + + int result = 0; + + for (PoolPartition partition : partitions) { + if (partition != null) { + result += partition.currentSize.get() - partition.acquiredObjects.get(); + } + } + + if (result < 0) + return 0; + + return result; + } + + public int getCreatedInstances() { + checkForClose(); + + int result = 0; + + for (PoolPartition partition : partitions) { + if (partition != null) { + result += partition.currentSize.get(); + } + } + + return result; + } + + private void initQueue(PoolPartition partition) { + ConcurrentLinkedQueue queue = partition.queue; + + for (int n = 0; n < MIN_POOL_SIZE; n++) { + final T object = factory.create(); + queue.add(object); + } + + partition.currentSize.addAndGet(MIN_POOL_SIZE); + } + + private void checkForClose() { + if (closed) + throw new IllegalStateException("Pool is closed"); + } + + private static int nextHashCode() { + return nextHashCode.getAndAdd(HASH_INCREMENT); + } + + private static final class PoolPartition { + private final AtomicInteger currentSize = new AtomicInteger(); + private final AtomicInteger acquiredObjects = new AtomicInteger(); + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + } + + public interface ObjectFactory { + T create(); + + void init(T object); + + void close(T object); + + boolean isValid(T object); + } + + public static final class PoolEntry { + private final PoolPartition partition; + public final T object; + + public PoolEntry(PoolPartition partition, T object) { + this.partition = partition; + this.object = object; + } + } + + private static class ThreadHashCodeThreadLocal extends ThreadLocal { + @Override + protected Integer initialValue() { + return nextHashCode(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPoolFactory.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPoolFactory.java new file mode 100644 index 00000000000..b4a5bd7d4bb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OPartitionedObjectPoolFactory.java @@ -0,0 +1,132 @@ +package com.orientechnologies.common.concur.resource; + +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.googlecode.concurrentlinkedhashmap.EvictionListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +/** + * This is internal API, do not use it. + * + * @author Andrey Lomakin Andrey Lomakin + * @since 15/12/14 + */ +public class OPartitionedObjectPoolFactory extends OOrientListenerAbstract { + private volatile int maxPartitions = Runtime.getRuntime().availableProcessors() << 3; + private volatile int maxPoolSize = 64; + private boolean closed = false; + + private final ConcurrentLinkedHashMap> poolStore; + private final ObjectFactoryFactory objectFactoryFactory; + + private final EvictionListener> evictionListener = new EvictionListener>() { + @Override + public void onEviction( + K key, + OPartitionedObjectPool partitionedObjectPool) { + partitionedObjectPool.close(); + } + }; + + public OPartitionedObjectPoolFactory(final ObjectFactoryFactory objectFactoryFactory) { + this(objectFactoryFactory, 100); + } + + public OPartitionedObjectPoolFactory(final ObjectFactoryFactory objectFactoryFactory, final int capacity) { + this.objectFactoryFactory = objectFactoryFactory; + poolStore = new ConcurrentLinkedHashMap.Builder>().maximumWeightedCapacity(capacity) + .listener(evictionListener).build(); + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + public int getMaxPoolSize() { + return maxPoolSize; + } + + public void setMaxPoolSize(final int maxPoolSize) { + checkForClose(); + + this.maxPoolSize = maxPoolSize; + } + + public OPartitionedObjectPool get(final K key) { + checkForClose(); + + OPartitionedObjectPool pool = poolStore.get(key); + if (pool != null) + return pool; + + pool = new OPartitionedObjectPool(objectFactoryFactory.create(key), maxPoolSize, maxPartitions); + + final OPartitionedObjectPool oldPool = poolStore.putIfAbsent(key, pool); + if (oldPool != null) { + pool.close(); + return oldPool; + } + + return pool; + } + + public int getMaxPartitions() { + return maxPartitions; + } + + public void setMaxPartitions(final int maxPartitions) { + this.maxPartitions = maxPartitions; + } + + public Collection> getPools() { + checkForClose(); + + return Collections.unmodifiableCollection(poolStore.values()); + } + + public void close() { + if (closed) + return; + + closed = true; + + while (!poolStore.isEmpty()) { + final Iterator> poolIterator = poolStore.values().iterator(); + + while (poolIterator.hasNext()) { + final OPartitionedObjectPool pool = poolIterator.next(); + + try { + pool.close(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during pool close", e); + } + + poolIterator.remove(); + } + } + + for (OPartitionedObjectPool pool : poolStore.values()) + pool.close(); + + poolStore.clear(); + } + + @Override + public void onShutdown() { + close(); + } + + private void checkForClose() { + if (closed) + throw new IllegalStateException("Pool factory is closed"); + } + + public interface ObjectFactoryFactory { + OPartitionedObjectPool.ObjectFactory create(K key); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OReentrantResourcePool.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OReentrantResourcePool.java new file mode 100755 index 00000000000..01237daea26 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OReentrantResourcePool.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import com.orientechnologies.common.concur.lock.OLockException; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Reentrant implementation of Resource Pool. It manages multiple resource acquisition on thread local map. If you're looking for a + * Reentrant implementation look at #OReentrantResourcePool. + * + * @author Andrey Lomakin (a.lomakin--at--orientechnologies.com) + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @see OResourcePool + */ +public class OReentrantResourcePool extends OResourcePool implements OOrientStartupListener, OOrientShutdownListener { + private volatile ThreadLocal>> activeResources = new ThreadLocal>>(); + + private static final class ResourceHolder { + private final V resource; + private int counter = 1; + + private ResourceHolder(V resource) { + this.resource = resource; + } + } + + public OReentrantResourcePool(final int maxResources, final OResourcePoolListener listener) { + super(maxResources, listener); + + Orient.instance().registerWeakOrientShutdownListener(this); + Orient.instance().registerWeakOrientStartupListener(this); + } + + @Override + public void onShutdown() { + activeResources = null; + } + + @Override + public void onStartup() { + if (activeResources == null) + activeResources = new ThreadLocal>>(); + } + + public V getResource(K key, final long maxWaitMillis, Object... additionalArgs) throws OLockException { + Map> resourceHolderMap = activeResources.get(); + + if (resourceHolderMap == null) { + resourceHolderMap = new HashMap>(); + activeResources.set(resourceHolderMap); + } + + final ResourceHolder holder = resourceHolderMap.get(key); + if (holder != null) { + holder.counter++; + return holder.resource; + } + try { + final V res = super.getResource(key, maxWaitMillis, additionalArgs); + resourceHolderMap.put(key, new ResourceHolder(res)); + return res; + + } catch (RuntimeException e) { + resourceHolderMap.remove(key); + + // PROPAGATE IT + throw e; + } + } + + public boolean returnResource(final V res) { + final Map> resourceHolderMap = activeResources.get(); + if (resourceHolderMap != null) { + K keyToRemove = null; + for (Map.Entry> entry : resourceHolderMap.entrySet()) { + final ResourceHolder holder = entry.getValue(); + if (holder.resource.equals(res)) { + holder.counter--; + assert holder.counter >= 0; + if (holder.counter > 0) + return false; + + keyToRemove = entry.getKey(); + break; + } + } + + resourceHolderMap.remove(keyToRemove); + } + + return super.returnResource(res); + } + + public int getConnectionsInCurrentThread(final K key) { + final Map> resourceHolderMap = activeResources.get(); + if (resourceHolderMap == null) + return 0; + + final ResourceHolder holder = resourceHolderMap.get(key); + if (holder == null) + return 0; + + return holder.counter; + } + + public void remove(final V res) { + this.resources.remove(res); + + final List activeResourcesToRemove = new ArrayList(); + final Map> activeResourcesMap = activeResources.get(); + + if (activeResourcesMap != null) { + for (Map.Entry> entry : activeResourcesMap.entrySet()) { + final ResourceHolder holder = entry.getValue(); + if (holder.resource.equals(res)) + activeResourcesToRemove.add(entry.getKey()); + } + + for (K resourceKey : activeResourcesToRemove) { + activeResourcesMap.remove(resourceKey); + sem.release(); + } + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePool.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePool.java new file mode 100755 index 00000000000..f47a18d8818 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePool.java @@ -0,0 +1,163 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import com.orientechnologies.common.concur.lock.OInterruptedException; +import com.orientechnologies.common.concur.lock.OLockException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Generic non reentrant implementation about pool of resources. It pre-allocates a semaphore of maxResources. Resources are lazily + * created by invoking the listener. + * + * @param + * Resource's Key + * @param + * Resource Object + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OResourcePool { + protected final Semaphore sem; + protected final Queue resources = new ConcurrentLinkedQueue(); + protected final Queue resourcesOut = new ConcurrentLinkedQueue(); + protected final Collection unmodifiableresources; + private final int maxResources; + protected OResourcePoolListener listener; + protected final AtomicInteger created = new AtomicInteger(); + + public OResourcePool(final int iMaxResources, final OResourcePoolListener listener) { + maxResources = iMaxResources; + if (maxResources < 1) + throw new IllegalArgumentException("iMaxResource must be major than 0"); + + this.listener = listener; + sem = new Semaphore(maxResources, true); + unmodifiableresources = Collections.unmodifiableCollection(resources); + } + + public V getResource(K key, final long maxWaitMillis, Object... additionalArgs) throws OLockException { + // First, get permission to take or create a resource + try { + if (!sem.tryAcquire(maxWaitMillis, TimeUnit.MILLISECONDS)) + throw new OLockException("No more resources available in pool (max=" + maxResources + "). Requested resource: " + key); + + } catch (InterruptedException e) { + throw new OInterruptedException("Acquiring of resources was interrupted"); + } + + V res; + do { + // POP A RESOURCE + res = resources.poll(); + if (res != null) { + // TRY TO REUSE IT + if (listener.reuseResource(key, additionalArgs, res)) { + // OK: REUSE IT + break; + } else + res = null; + + // UNABLE TO REUSE IT: THE RESOURE WILL BE DISCARDED AND TRY WITH THE NEXT ONE, IF ANY + } + } while (!resources.isEmpty()); + + // NO AVAILABLE RESOURCES: CREATE A NEW ONE + try { + if (res == null) { + res = listener.createNewResource(key, additionalArgs); + created.incrementAndGet(); + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "pool:'%s' created new resource '%s', new resource count '%d'", this, res, + created.get()); + } + resourcesOut.add(res); + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "pool:'%s' acquired resource '%s' available %d out %d ", this, res, + sem.availablePermits(), resourcesOut.size()); + return res; + } catch (RuntimeException e) { + sem.release(); + // PROPAGATE IT + throw e; + } catch (Exception e) { + sem.release(); + + throw OException.wrapException(new OLockException("Error on creation of the new resource in the pool"), e); + } + } + + public int getMaxResources() { + return maxResources; + } + + public int getAvailableResources() { + return sem.availablePermits(); + } + + public int getInPoolResources() { + return resources.size(); + } + + public boolean returnResource(final V res) { + if (resourcesOut.remove(res)) { + resources.add(res); + sem.release(); + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "pool:'%s' returned resource '%s' available %d out %d", this, res, + sem.availablePermits(), resourcesOut.size()); + } + return true; + } + + public Collection getResources() { + return unmodifiableresources; + } + + public void close() { + sem.drainPermits(); + } + + public Collection getAllResources() { + List all = new ArrayList(resources); + all.addAll(resourcesOut); + return all; + } + + public void remove(final V res) { + if (resourcesOut.remove(res)) { + this.resources.remove(res); + sem.release(); + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "pool:'%s' removed resource '%s' available %d out %d", this, res, + sem.availablePermits(), resourcesOut.size()); + } + } + + public int getCreatedInstances() { + return created.get(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePoolListener.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePoolListener.java new file mode 100644 index 00000000000..ba2ba88c0a4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OResourcePoolListener.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +/** + * Interface to manage resources in the pool. + * + * @author Luca Garulli + */ +public interface OResourcePoolListener { + + /** + * Creates a new resource to be used and to be pooled when the client finishes with it. + * + * @return The new resource + */ + V createNewResource(K iKey, Object... iAdditionalArgs); + + /** + * Reuses the pooled resource. + * + * @return true if can be reused, otherwise false. In this case the resource will be removed from the pool + */ + boolean reuseResource(K iKey, Object[] iAdditionalArgs, V iValue); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainer.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainer.java new file mode 100644 index 00000000000..d4a4e653b19 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainer.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import java.util.concurrent.Callable; + +/** + * Shared container interface that works with callbacks like closures. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OSharedContainer { + public boolean existsResource(final String iName); + + public T removeResource(final String iName); + + public T getResource(final String iName, final Callable iCallback); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainerImpl.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainerImpl.java new file mode 100755 index 00000000000..1794df62e10 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedContainerImpl.java @@ -0,0 +1,88 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.ODatabaseException; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Shared container that works with callbacks like closures. If the resource implements the {@link OSharedResource} interface then + * the resource is locked until is removed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OSharedContainerImpl implements OSharedContainer { + protected Map sharedResources = new ConcurrentHashMap(); + + public boolean existsResource(final String iName) { + // BYPASS THE SYNCHRONIZED BLOCK BECAUSE THE MAP IS ALREADY SYNCHRONIZED + return sharedResources.containsKey(iName); + } + + public T removeResource(final String iName) { + synchronized (this) { + T resource = (T) sharedResources.remove(iName); + + if (resource instanceof OSharedResource) + ((OSharedResource) resource).releaseExclusiveLock(); + return resource; + } + } + + public T getResource(final String iName, final Callable iCallback) { + T value = (T) sharedResources.get(iName); + if (value == null) { + // THE SYNCHRONIZED BLOCK I CREATES NEEDED ONLY TO PREVENT MULTIPLE CALL TO THE CALLBACK IN CASE OF CONCURRENT + synchronized (this) { + if (value == null) { + // CREATE IT + try { + value = iCallback.call(); + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Error on creation of shared resource"), e); + } + + if (value instanceof OSharedResource) + ((OSharedResource) value).acquireExclusiveLock(); + + sharedResources.put(iName, value); + } + } + } + + return value; + } + + public void clearResources() { + synchronized (this) { + for (Object resource : sharedResources.values()) { + if (resource instanceof OCloseable) + (((OCloseable) resource)).close(); + } + + sharedResources.clear(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResource.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResource.java new file mode 100644 index 00000000000..9af70a7bee8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResource.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +/** + * Shared resource interface. Implementations can acquire and release shared and exclusive locks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OSharedResource { + void acquireSharedLock(); + + void releaseSharedLock(); + + void acquireExclusiveLock(); + + void releaseExclusiveLock(); +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAbstract.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAbstract.java new file mode 100644 index 00000000000..238b9a6ae4b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAbstract.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Shared resource abstract class. Sub classes can acquire and release shared and exclusive locks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSharedResourceAbstract { + protected final ReadWriteLock lock = new ReentrantReadWriteLock(); + + protected void acquireSharedLock() { + lock.readLock().lock(); + } + + protected void releaseSharedLock() { + lock.readLock().unlock(); + } + + protected void acquireExclusiveLock() { + lock.writeLock().lock(); + } + + protected void releaseExclusiveLock() { + lock.writeLock().unlock(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptive.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptive.java new file mode 100755 index 00000000000..5149e1a43ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptive.java @@ -0,0 +1,230 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import com.orientechnologies.common.concur.OTimeoutException; +import com.orientechnologies.common.concur.lock.OLockException; +import com.orientechnologies.common.exception.OException; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Adaptive class to handle shared resources. It's configurable specifying if it's running in a concurrent environment and allow o + * specify a maximum timeout to avoid deadlocks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSharedResourceAdaptive { + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final AtomicInteger users = new AtomicInteger(0); + private final boolean concurrent; + private final int timeout; + private final boolean ignoreThreadInterruption; + + protected OSharedResourceAdaptive() { + this.concurrent = true; + this.timeout = 0; + this.ignoreThreadInterruption = false; + } + + protected OSharedResourceAdaptive(final int iTimeout) { + this.concurrent = true; + this.timeout = iTimeout; + this.ignoreThreadInterruption = false; + } + + protected OSharedResourceAdaptive(final boolean iConcurrent) { + this.concurrent = iConcurrent; + this.timeout = 0; + this.ignoreThreadInterruption = false; + } + + protected OSharedResourceAdaptive(final boolean iConcurrent, final int iTimeout, boolean ignoreThreadInterruption) { + this.concurrent = iConcurrent; + this.timeout = iTimeout; + this.ignoreThreadInterruption = ignoreThreadInterruption; + } + + public int getUsers() { + return users.get(); + } + + public int addUser() { + return users.incrementAndGet(); + } + + public int removeUser() { + if (users.get() < 1) + throw new IllegalStateException("Cannot remove user of the shared resource " + toString() + " because no user is using it"); + + return users.decrementAndGet(); + } + + public boolean isConcurrent() { + return concurrent; + } + + /** To use in assert block. */ + public boolean assertExclusiveLockHold() { + return lock.getWriteHoldCount() > 0; + } + + /** To use in assert block. */ + public boolean assertSharedLockHold() { + return lock.getReadHoldCount() > 0; + } + + protected void acquireExclusiveLock() { + if (concurrent) + if (timeout > 0) { + try { + + if (lock.writeLock().tryLock(timeout, TimeUnit.MILLISECONDS)) + // OK + return; + } catch (InterruptedException e) { + if (ignoreThreadInterruption) { + // IGNORE THE THREAD IS INTERRUPTED: TRY TO RE-LOCK AGAIN + try { + if (lock.writeLock().tryLock(timeout, TimeUnit.MILLISECONDS)) { + // OK, RESET THE INTERRUPTED STATE + Thread.currentThread().interrupt(); + return; + } + } catch (InterruptedException e2) { + Thread.currentThread().interrupt(); + } + } + + final OLockException exception = new OLockException("Thread interrupted while waiting for resource of class '" + + getClass() + "' with timeout=" + timeout); + throw OException.wrapException(exception, e); + + } + throwTimeoutException(lock.writeLock()); + } else { + lock.writeLock().lock(); + } + } + + protected boolean tryAcquireExclusiveLock() { + return !concurrent || lock.writeLock().tryLock(); + } + + protected void acquireSharedLock() { + if (concurrent) + if (timeout > 0) { + try { + if (lock.readLock().tryLock(timeout, TimeUnit.MILLISECONDS)) + // OK + return; + } catch (InterruptedException e) { + if (ignoreThreadInterruption) { + // IGNORE THE THREAD IS INTERRUPTED: TRY TO RE-LOCK AGAIN + try { + if (lock.readLock().tryLock(timeout, TimeUnit.MILLISECONDS)) { + // OK, RESET THE INTERRUPTED STATE + Thread.currentThread().interrupt(); + return; + } + } catch (InterruptedException e2) { + Thread.currentThread().interrupt(); + } + } + + final OLockException exception = new OLockException("Thread interrupted while waiting for resource of class '" + + getClass() + "' with timeout=" + timeout); + throw OException.wrapException(exception, e); + } + + throwTimeoutException(lock.readLock()); + } else + lock.readLock().lock(); + } + + protected boolean tryAcquireSharedLock() { + return !concurrent || lock.readLock().tryLock(); + } + + protected void releaseExclusiveLock() { + if (concurrent) { + lock.writeLock().unlock(); + } + } + + protected void releaseSharedLock() { + if (concurrent) + lock.readLock().unlock(); + } + + private void throwTimeoutException(Lock lock) { + final String owner = extractLockOwnerStackTrace(lock); + + throw new OTimeoutException("Timeout on acquiring exclusive lock against resource of class: " + getClass() + " with timeout=" + + timeout + (owner != null ? "\n" + owner : "")); + } + + private String extractLockOwnerStackTrace(Lock lock) { + try { + Field syncField = lock.getClass().getDeclaredField("sync"); + syncField.setAccessible(true); + + Object sync = syncField.get(lock); + Method getOwner = sync.getClass().getSuperclass().getDeclaredMethod("getOwner"); + getOwner.setAccessible(true); + + final Thread owner = (Thread) getOwner.invoke(sync); + if (owner == null) + return null; + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + printWriter.append("Owner thread : ").append(owner.toString()).append("\n"); + + StackTraceElement[] stackTrace = owner.getStackTrace(); + for (StackTraceElement traceElement : stackTrace) + printWriter.println("\tat " + traceElement); + + printWriter.flush(); + return stringWriter.toString(); + } catch (RuntimeException e) { + return null; + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptiveExternal.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptiveExternal.java new file mode 100644 index 00000000000..0d9c23885d4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceAdaptiveExternal.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +/** + * Optimize locks since they are enabled only when the engine runs in MULTI-THREADS mode. + * + * @author Luca Garulli + * + */ +public class OSharedResourceAdaptiveExternal extends OSharedResourceAdaptive implements OSharedResource { + public OSharedResourceAdaptiveExternal(final boolean iConcurrent, final int iTimeout, final boolean ignoreThreadInterruption) { + super(iConcurrent, iTimeout, ignoreThreadInterruption); + } + + @Override + public void acquireExclusiveLock() { + super.acquireExclusiveLock(); + } + + public boolean tryAcquireExclusiveLock() { + return super.tryAcquireExclusiveLock(); + } + + @Override + public void acquireSharedLock() { + super.acquireSharedLock(); + } + + public boolean tryAcquireSharedLock() { + return super.tryAcquireSharedLock(); + } + + @Override + public void releaseExclusiveLock() { + super.releaseExclusiveLock(); + } + + @Override + public void releaseSharedLock() { + super.releaseSharedLock(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceTimeout.java b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceTimeout.java new file mode 100644 index 00000000000..91d168954fb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/concur/resource/OSharedResourceTimeout.java @@ -0,0 +1,130 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.concur.resource; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.orientechnologies.common.concur.OTimeoutException; + +/** + * Shared resource. Sub classes can acquire and release shared and exclusive locks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSharedResourceTimeout { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + protected int timeout; + + public OSharedResourceTimeout(final int timeout) { + this.timeout = timeout; + } + + protected void acquireSharedLock() throws OTimeoutException { + try { + if (timeout == 0) { + lock.readLock().lock(); + return; + } else if (lock.readLock().tryLock(timeout, TimeUnit.MILLISECONDS)) + // OK + return; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + throwTimeoutException(lock.readLock()); + } + + protected void releaseSharedLock() { + lock.readLock().unlock(); + } + + protected void acquireExclusiveLock() throws OTimeoutException { + try { + if (timeout == 0) { + lock.writeLock().lock(); + return; + } else if (lock.writeLock().tryLock(timeout, TimeUnit.MILLISECONDS)) + // OK + return; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + throwTimeoutException(lock.writeLock()); + } + + protected void releaseExclusiveLock() { + lock.writeLock().unlock(); + } + + private void throwTimeoutException(Lock lock) { + final String owner = extractLockOwnerStackTrace(lock); + + throw new OTimeoutException("Timeout on acquiring exclusive lock against resource of class: " + getClass() + " with timeout=" + + timeout + (owner != null ? "\n" + owner : "")); + } + + private String extractLockOwnerStackTrace(Lock lock) { + try { + Field syncField = lock.getClass().getDeclaredField("sync"); + syncField.setAccessible(true); + + Object sync = syncField.get(lock); + Method getOwner = sync.getClass().getSuperclass().getDeclaredMethod("getOwner"); + getOwner.setAccessible(true); + + final Thread owner = (Thread) getOwner.invoke(sync); + if (owner == null) + return null; + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + printWriter.append("Owner thread : ").append(owner.toString()).append("\n"); + + StackTraceElement[] stackTrace = owner.getStackTrace(); + for (StackTraceElement traceElement : stackTrace) + printWriter.println("\tat " + traceElement); + + printWriter.flush(); + return stringWriter.toString(); + } catch (RuntimeException e) { + return null; + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/console/OCommandStream.java b/core/src/main/java/com/orientechnologies/common/console/OCommandStream.java new file mode 100755 index 00000000000..d131bef0675 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/OCommandStream.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import com.orientechnologies.common.concur.resource.OCloseable; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public interface OCommandStream extends OCloseable { + boolean hasNext(); + + String nextCommand(); +} diff --git a/core/src/main/java/com/orientechnologies/common/console/OConsoleApplication.java b/core/src/main/java/com/orientechnologies/common/console/OConsoleApplication.java new file mode 100755 index 00000000000..0e0a270ae34 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/OConsoleApplication.java @@ -0,0 +1,735 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.console; + +import com.orientechnologies.common.console.annotation.ConsoleCommand; +import com.orientechnologies.common.console.annotation.ConsoleParameter; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.parser.OStringParser; +import com.orientechnologies.common.util.OArrays; + +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class OConsoleApplication { + protected static final String[] COMMENT_PREFIXS = new String[] { "#", "--", "//" }; + public static final String ONLINE_HELP_URL = "https://raw.githubusercontent.com/orientechnologies/orientdb-docs/master/"; + public static final String ONLINE_HELP_EXT = ".md"; + protected final StringBuilder commandBuffer = new StringBuilder(2048); + protected InputStream in = System.in; // System.in; + protected PrintStream out = System.out; + protected PrintStream err = System.err; + protected String wordSeparator = " "; + protected String[] helpCommands = { "help", "?" }; + protected String[] exitCommands = { "exit", "bye", "quit" }; + protected Map properties = new HashMap(); + protected OConsoleReader reader = new ODefaultConsoleReader(); + protected boolean interactiveMode; + protected String[] args; + protected TreeMap methods; + protected boolean debugMode; + + protected enum RESULT { + OK, ERROR, EXIT + } + + public OConsoleApplication(String[] iArgs) { + this.args = iArgs; + + debugMode = Boolean.valueOf(System.getProperty("debugMode")); + } + + public static String getCorrectMethodName(Method m) { + StringBuilder buffer = new StringBuilder(128); + buffer.append(getClearName(m.getName())); + for (int i = 0; i < m.getParameterAnnotations().length; i++) { + for (int j = 0; j < m.getParameterAnnotations()[i].length; j++) { + if (m.getParameterAnnotations()[i][j] instanceof com.orientechnologies.common.console.annotation.ConsoleParameter) { + buffer.append( + " <" + ((com.orientechnologies.common.console.annotation.ConsoleParameter) m.getParameterAnnotations()[i][j]).name() + + ">"); + } + } + } + return buffer.toString(); + } + + public static String getClearName(String iJavaName) { + StringBuilder buffer = new StringBuilder(); + + char c; + if (iJavaName != null) { + buffer.append(iJavaName.charAt(0)); + for (int i = 1; i < iJavaName.length(); ++i) { + c = iJavaName.charAt(i); + + if (Character.isUpperCase(c)) { + buffer.append(' '); + } + + buffer.append(Character.toLowerCase(c)); + } + + } + return buffer.toString(); + } + + public void setReader(OConsoleReader iReader) { + this.reader = iReader; + reader.setConsole(this); + } + + public int run() { + interactiveMode = isInteractiveMode(args); + onBefore(); + + int result = 0; + + if (interactiveMode) { + // EXECUTE IN INTERACTIVE MODE + // final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + String consoleInput = null; + + while (true) { + try { + if (commandBuffer.length() == 0) { + out.println(); + out.print(getPrompt()); + } + + consoleInput = reader.readLine(); + + if (consoleInput == null || consoleInput.length() == 0) + continue; + + if (!executeCommands(new ODFACommandStream(consoleInput), false)) + break; + } catch (Exception e) { + result = 1; + out.print("Error on reading console input: " + e.getMessage()); + OLogManager.instance().error(this, "Error on reading console input: %s", e, consoleInput); + } + } + } else { + // EXECUTE IN BATCH MODE + result = executeBatch(getCommandLine(args)) ? 0 : 1; + } + + onAfter(); + + return result; + } + + public void message(final String iMessage, final Object... iArgs) { + final int verboseLevel = getVerboseLevel(); + if (verboseLevel > 1) { + if (iArgs != null && iArgs.length > 0) + out.printf(iMessage, iArgs); + else + out.print(iMessage); + } + } + + public void error(final String iMessage, final Object... iArgs) { + final int verboseLevel = getVerboseLevel(); + if (verboseLevel > 0) { + if (iArgs != null && iArgs.length > 0) + out.printf(iMessage, iArgs); + else + out.print(iMessage); + } + } + + public int getVerboseLevel() { + final String v = properties.get("verbose"); + final int verboseLevel = v != null ? Integer.parseInt(v) : 2; + return verboseLevel; + } + + protected int getConsoleWidth() { + final String width = properties.get("width"); + return width == null ? reader.getConsoleWidth() : Integer.parseInt(width); + } + + public boolean isEchoEnabled() { + return isPropertyEnabled("echo"); + } + + protected boolean isPropertyEnabled(final String iPropertyName) { + String v = properties.get(iPropertyName); + if (v != null) { + v = v.toLowerCase(Locale.ENGLISH); + return v.equals("true") || v.equals("on"); + } + return false; + } + + protected String getPrompt() { + return String.format("%s> ", getContext()); + } + + protected String getContext() { + return ""; + } + + protected boolean isInteractiveMode(String[] args) { + return args.length == 0; + } + + protected boolean executeBatch(final String commandLine) { + File commandFile = new File(commandLine); + if (!commandFile.isAbsolute()) { + commandFile = new File(new File("."), commandLine); + } + + OCommandStream scanner; + try { + scanner = new ODFACommandStream(commandFile); + } catch (FileNotFoundException e) { + scanner = new ODFACommandStream(commandLine); + } + + return executeCommands(scanner, true); + } + + protected boolean executeCommands(final OCommandStream commandStream, final boolean iBatchMode) { + try { + while (commandStream.hasNext()) { + String commandLine = commandStream.nextCommand(); + + if (commandLine.isEmpty()) + // EMPTY LINE + continue; + + if (isComment(commandLine)) + continue; + + // SCRIPT CASE: MANAGE ENSEMBLING ALL TOGETHER + if (isCollectingCommands(commandLine)) { + // BEGIN: START TO COLLECT + out.println("[Started multi-line command. Type just 'end' to finish and execute]"); + commandBuffer.append(commandLine); + commandLine = null; + } else if (commandLine.startsWith("end") && commandBuffer.length() > 0) { + // END: FLUSH IT + commandLine = commandBuffer.toString(); + commandBuffer.setLength(0); + + } else if (commandBuffer.length() > 0) { + // BUFFER IT + commandBuffer.append(' '); + commandBuffer.append(commandLine); + commandBuffer.append(';'); + commandLine = null; + } + + if (commandLine != null) { + if (iBatchMode || isEchoEnabled()) { + out.println(); + out.print(getPrompt()); + out.print(commandLine); + out.println(); + } + + final RESULT status = execute(commandLine); + commandLine = null; + + if (status == RESULT.EXIT + || (status == RESULT.ERROR && !Boolean.parseBoolean(properties.get("ignoreErrors"))) && iBatchMode) + return false; + } + } + + if (commandBuffer.length() == 0) { + if (commandBuffer.length() > 0) { + if (iBatchMode) { + out.println(); + out.print(getPrompt()); + out.print(commandBuffer); + out.println(); + } + + final RESULT status = execute(commandBuffer.toString()); + if (status == RESULT.EXIT + || (status == RESULT.ERROR && !Boolean.parseBoolean(properties.get("ignoreErrors"))) && iBatchMode) + return false; + } + } + } finally { + commandStream.close(); + } + return true; + } + + protected boolean isComment(final String commandLine) { + for (String comment : COMMENT_PREFIXS) + if (commandLine.startsWith(comment)) + return true; + return false; + } + + protected boolean isCollectingCommands(final String iLine) { + return false; + } + + protected RESULT execute(String iCommand) { + iCommand = iCommand.trim(); + + if (iCommand.length() == 0) + // NULL LINE: JUMP IT + return RESULT.OK; + + if (isComment(iCommand)) + // COMMENT: JUMP IT + return RESULT.OK; + + String[] commandWords = OStringParser.getWords(iCommand, wordSeparator); + + for (String cmd : helpCommands) + if (cmd.equals(commandWords[0])) { + if (iCommand.length() > cmd.length()) + help(iCommand.substring(cmd.length() + 1)); + else + help(null); + + return RESULT.OK; + } + + for (String cmd : exitCommands) + if (cmd.equalsIgnoreCase(commandWords[0])) { + return RESULT.EXIT; + } + + Method lastMethodInvoked = null; + final StringBuilder lastCommandInvoked = new StringBuilder(1024); + + String commandLowerCase = ""; + for (int i = 0; i < commandWords.length; i++) { + if (i > 0) { + commandLowerCase += " "; + } + commandLowerCase += commandWords[i].toLowerCase(Locale.ENGLISH); + } + + for (Entry entry : getConsoleMethods().entrySet()) { + final Method m = entry.getKey(); + final String methodName = m.getName(); + final ConsoleCommand ann = m.getAnnotation(ConsoleCommand.class); + + final StringBuilder commandName = new StringBuilder(); + char ch; + int commandWordCount = 1; + for (int i = 0; i < methodName.length(); ++i) { + ch = methodName.charAt(i); + if (Character.isUpperCase(ch)) { + commandName.append(" "); + ch = Character.toLowerCase(ch); + commandWordCount++; + } + commandName.append(ch); + } + + if (!commandLowerCase.equals(commandName.toString()) && !commandLowerCase.startsWith(commandName.toString() + " ")) { + if (ann == null) + continue; + + String[] aliases = ann.aliases(); + if (aliases == null || aliases.length == 0) + continue; + + boolean aliasMatch = false; + for (String alias : aliases) { + if (iCommand.startsWith(alias.split(" ")[0])) { + aliasMatch = true; + commandWordCount = 1; + break; + } + } + + if (!aliasMatch) + continue; + } + + Object[] methodArgs; + + // BUILD PARAMETERS + if (ann != null && !ann.splitInWords()) { + methodArgs = new String[] { iCommand.substring(iCommand.indexOf(' ') + 1) }; + } else { + final int actualParamCount = commandWords.length - commandWordCount; + if (m.getParameterTypes().length > actualParamCount) { + // METHOD PARAMS AND USED PARAMS MISMATCH: CHECK FOR OPTIONALS + for (int paramNum = m.getParameterAnnotations().length - 1; paramNum > actualParamCount - 1; paramNum--) { + final Annotation[] paramAnn = m.getParameterAnnotations()[paramNum]; + if (paramAnn != null) + for (int annNum = paramAnn.length - 1; annNum > -1; annNum--) { + if (paramAnn[annNum] instanceof ConsoleParameter) { + final ConsoleParameter annotation = (ConsoleParameter) paramAnn[annNum]; + if (annotation.optional()) + commandWords = OArrays.copyOf(commandWords, commandWords.length + 1); + break; + } + } + } + } + methodArgs = OArrays.copyOfRange(commandWords, commandWordCount, commandWords.length); + } + + try { + m.invoke(entry.getValue(), methodArgs); + + } catch (IllegalArgumentException e) { + lastMethodInvoked = m; + // GET THE COMMAND NAME + lastCommandInvoked.setLength(0); + for (int i = 0; i < commandWordCount; ++i) { + if (lastCommandInvoked.length() > 0) + lastCommandInvoked.append(" "); + lastCommandInvoked.append(commandWords[i]); + } + continue; + } catch (Exception e) { + if (e.getCause() != null) + onException(e.getCause()); + else + e.printStackTrace(err); + return RESULT.ERROR; + } + return RESULT.OK; + } + + if (lastMethodInvoked != null) + syntaxError(lastCommandInvoked.toString(), lastMethodInvoked); + + error("\n!Unrecognized command: '%s'", iCommand); + return RESULT.ERROR; + } + + protected Method getMethod(String iCommand) { + iCommand = iCommand.trim(); + + if (iCommand.length() == 0) + // NULL LINE: JUMP IT + return null; + + if (isComment(iCommand)) + // COMMENT: JUMP IT + return null; + + final String commandLowerCase = iCommand.toLowerCase(Locale.ENGLISH); + + final Map methodMap = getConsoleMethods(); + + final StringBuilder commandSignature = new StringBuilder(); + boolean separator = false; + for (int i = 0; i < iCommand.length(); ++i) { + final char ch = iCommand.charAt(i); + if (ch == ' ') + separator = true; + else { + if (separator) { + separator = false; + commandSignature.append(Character.toUpperCase(ch)); + } else + commandSignature.append(ch); + } + } + + final String commandSignatureToCheck = commandSignature.toString(); + + for (Entry entry : methodMap.entrySet()) { + final Method m = entry.getKey(); + if (m.getName().equals(commandSignatureToCheck)) + // FOUND EXACT MATCH + return m; + } + + for (Entry entry : methodMap.entrySet()) { + final Method m = entry.getKey(); + final String methodName = m.getName(); + final ConsoleCommand ann = m.getAnnotation(ConsoleCommand.class); + + final StringBuilder commandName = new StringBuilder(); + char ch; + for (int i = 0; i < methodName.length(); ++i) { + ch = methodName.charAt(i); + if (Character.isUpperCase(ch)) { + commandName.append(" "); + ch = Character.toLowerCase(ch); + } + commandName.append(ch); + } + + if (!commandLowerCase.equals(commandName.toString()) && !commandLowerCase.startsWith(commandName.toString() + " ")) { + if (ann == null) + continue; + + String[] aliases = ann.aliases(); + if (aliases == null || aliases.length == 0) + continue; + + for (String alias : aliases) { + if (iCommand.startsWith(alias.split(" ")[0])) { + return m; + } + } + } else + return m; + } + + error("\n!Unrecognized command: '%s'", iCommand); + return null; + } + + protected void syntaxError(String iCommand, Method m) { + error( + "\n!Wrong syntax. If you're running in batch mode make sure all commands are delimited by semicolon (;) or a linefeed (\\n). Expected: \n\r\n\r%s", + formatCommandSpecs(iCommand, m)); + } + + protected String formatCommandSpecs(final String iCommand, final Method m) { + final StringBuilder buffer = new StringBuilder(); + final StringBuilder signature = new StringBuilder(); + + signature.append(iCommand); + + String paramName = null; + String paramDescription = null; + boolean paramOptional = false; + + buffer.append("\n\nWHERE:\n\n"); + + for (Annotation[] annotations : m.getParameterAnnotations()) { + for (Annotation ann : annotations) { + if (ann instanceof com.orientechnologies.common.console.annotation.ConsoleParameter) { + paramName = ((com.orientechnologies.common.console.annotation.ConsoleParameter) ann).name(); + paramDescription = ((com.orientechnologies.common.console.annotation.ConsoleParameter) ann).description(); + paramOptional = ((com.orientechnologies.common.console.annotation.ConsoleParameter) ann).optional(); + break; + } + } + + if (paramName == null) + paramName = "?"; + + if (paramOptional) + signature.append(" [<" + paramName + ">]"); + else + signature.append(" <" + paramName + ">"); + + buffer.append("* "); + buffer.append(String.format("%-18s", paramName)); + + if (paramDescription != null) + buffer.append(paramDescription); + + if (paramOptional) + buffer.append(" (optional)"); + + buffer.append("\n"); + } + + signature.append(buffer); + + return signature.toString(); + } + + /** + * Returns a map of all console method and the object they can be called on. + * + * @return Map<Method,Object> + */ + protected Map getConsoleMethods() { + if (methods != null) + return methods; + + // search for declared command collections + final Iterator ite = ServiceLoader.load(OConsoleCommandCollection.class).iterator(); + final Collection candidates = new ArrayList(); + candidates.add(this); + while (ite.hasNext()) { + try { + // make a copy and set it's context + final OConsoleCommandCollection cc = ite.next().getClass().newInstance(); + cc.setContext(this); + candidates.add(cc); + } catch (InstantiationException ex) { + Logger.getLogger(OConsoleApplication.class.getName()).log(Level.WARNING, ex.getMessage()); + } catch (IllegalAccessException ex) { + Logger.getLogger(OConsoleApplication.class.getName()).log(Level.WARNING, ex.getMessage()); + } + } + + methods = new TreeMap(new Comparator() { + public int compare(Method o1, Method o2) { + final ConsoleCommand ann1 = o1.getAnnotation(ConsoleCommand.class); + final ConsoleCommand ann2 = o2.getAnnotation(ConsoleCommand.class); + + if (ann1 != null && ann2 != null) { + if (ann1.priority() != ann2.priority()) + // PRIORITY WINS + return ann1.priority() - ann2.priority(); + } + + int res = o1.getName().compareTo(o2.getName()); + if (res == 0) + res = o1.toString().compareTo(o2.toString()); + return res; + } + }); + + for (final Object candidate : candidates) { + final Method[] classMethods = candidate.getClass().getMethods(); + + for (Method m : classMethods) { + if (Modifier.isAbstract(m.getModifiers()) || Modifier.isStatic(m.getModifiers()) || !Modifier.isPublic(m.getModifiers())) { + continue; + } + if (m.getReturnType() != Void.TYPE) { + continue; + } + methods.put(m, candidate); + } + } + return methods; + } + + protected Map addCommand(Map commandsTree, String commandLine) { + return commandsTree; + } + + @ConsoleCommand(splitInWords = false, description = "Receives help on available commands or a specific one. Use 'help -online ' to fetch online documentation") + public void help(@ConsoleParameter(name = "command", description = "Command to receive help") String iCommand) { + if (iCommand == null || iCommand.trim().isEmpty()) { + // GENERIC HELP + message("\nAVAILABLE COMMANDS:\n"); + + for (Method m : getConsoleMethods().keySet()) { + ConsoleCommand annotation = m.getAnnotation(ConsoleCommand.class); + + if (annotation == null) + continue; + + message("* %-85s%s\n", getCorrectMethodName(m), annotation.description()); + } + message("* %-85s%s\n", getClearName("exit"), "Close the console"); + return; + } + + final String[] commandWords = OStringParser.getWords(iCommand, wordSeparator); + + boolean onlineMode = commandWords.length > 1 && commandWords[0].equalsIgnoreCase("-online"); + if (onlineMode) + iCommand = iCommand.substring("-online".length() + 1); + + final Method m = getMethod(iCommand); + if (m != null) { + final ConsoleCommand ann = m.getAnnotation(ConsoleCommand.class); + + message("\nCOMMAND: " + iCommand + "\n\n"); + if (ann != null) { + // FETCH ONLINE CONTENT + if (onlineMode && !ann.onlineHelp().isEmpty()) { + // try { + final String text = getOnlineHelp(ONLINE_HELP_URL + ann.onlineHelp() + ONLINE_HELP_EXT); + if (text != null && !text.isEmpty()) { + message(text); + // ONLINE FETCHING SUCCEED: RETURN + return; + } + // } catch (Exception e) { + // } + error("!CANNOT FETCH ONLINE DOCUMENTATION, CHECK IF COMPUTER IS CONNECTED TO THE INTERNET."); + return; + } + + message(ann.description() + "." + "\r\n\r\nSYNTAX: "); + + // IN ANY CASE DISPLAY INFORMATION BY READING ANNOTATIONS + message(formatCommandSpecs(iCommand, m)); + + } else + message("No description available"); + } + + } + + protected String getCommandLine(String[] iArguments) { + StringBuilder command = new StringBuilder(512); + for (int i = 0; i < iArguments.length; ++i) { + if (i > 0) + command.append(" "); + + command.append(iArguments[i]); + } + return command.toString(); + } + + protected void onBefore() { + } + + protected void onAfter() { + } + + protected void onException(final Throwable throwable) { + throwable.printStackTrace(err); + } + + public void setOutput(PrintStream iOut) { + this.out = iOut; + } + + protected String getOnlineHelp(final String urlToRead) { + URL url; + HttpURLConnection conn; + BufferedReader rd; + String line; + StringBuilder result = new StringBuilder(); + try { + url = new URL(urlToRead); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); + while ((line = rd.readLine()) != null) { + if (line.startsWith("```")) + continue; + else if (line.startsWith("# ")) + continue; + + if (result.length() > 0) + result.append("\n"); + + result.append(line); + } + rd.close(); + } catch (Exception e) { + } + return result.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/console/OConsoleCommandCollection.java b/core/src/main/java/com/orientechnologies/common/console/OConsoleCommandCollection.java new file mode 100644 index 00000000000..cd35e4a99e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/OConsoleCommandCollection.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Orient Technologies. + * Copyright 2012 Geomatys. + * + * Licensed 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 com.orientechnologies.common.console; + +import com.orientechnologies.common.console.annotation.ConsoleCommand; + +/** + * Commun interface for addtitional console commands. + * Instances of this class are discovered throught serviceLoaders. + * It should be declared in file : + * META-INF/services/com.orientechnologies.common.console.OConsoleCommandCollection + * + * This interface is empty, all wanted commands are expected to be annoted with + * {@link ConsoleCommand}. + * + * @author Johann Sorel (Geomatys) + */ +public abstract class OConsoleCommandCollection { + + protected OConsoleApplication context; + + void setContext(OConsoleApplication context){ + this.context = context; + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/console/OConsoleReader.java b/core/src/main/java/com/orientechnologies/common/console/OConsoleReader.java new file mode 100644 index 00000000000..45bfaf0f200 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/OConsoleReader.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import java.io.IOException; + +public interface OConsoleReader { + int FALLBACK_CONSOLE_WIDTH = 150; + + String readLine() throws IOException; + + void setConsole(OConsoleApplication console); + + String readPassword() throws IOException; + + int getConsoleWidth(); +} diff --git a/core/src/main/java/com/orientechnologies/common/console/ODFACommandStream.java b/core/src/main/java/com/orientechnologies/common/console/ODFACommandStream.java new file mode 100755 index 00000000000..d05fba7799f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/ODFACommandStream.java @@ -0,0 +1,257 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class ODFACommandStream implements OCommandStream { + public static final int BUFFER_SIZE = 1024; + private final Set separators = new HashSet(Arrays.asList(';', '\n')); + private Reader reader; + + private Character nextCharacter; + private State state; + + private enum State { + S, A, B, C, D, E, F + } + + private enum Symbol { + LATTER, WS, QT, AP, SEP, EOF + } + + public ODFACommandStream(String commands) { + reader = new StringReader(commands); + + init(); + } + + public ODFACommandStream(File file) throws FileNotFoundException { + reader = new BufferedReader(new FileReader(file), BUFFER_SIZE); + + init(); + } + + private void init() { + try { + final int next = reader.read(); + if (next > -1) + nextCharacter = (char) next; + else + nextCharacter = null; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Character nextCharacter() throws IOException { + if (nextCharacter == null) + return null; + + final Character result = nextCharacter; + final int next = reader.read(); + if (next < 0) + nextCharacter = null; + else + nextCharacter = (char) next; + + return result; + } + + @Override + public boolean hasNext() { + return nextCharacter != null; + } + + @Override + public String nextCommand() { + try { + state = State.S; + final StringBuilder result = new StringBuilder(); + + StringBuilder stateWord = new StringBuilder(); + + while (state != State.E) { + Character c = nextCharacter(); + String sch = null; + + Symbol s; + if (c == null) + s = Symbol.EOF; + else if (c.equals('\'')) + s = Symbol.AP; + else if (c.equals('"')) + s = Symbol.QT; + else if (separators.contains(c)) + s = Symbol.SEP; + else if (Character.isWhitespace(c)) + s = Symbol.WS; + else if (c == '\\') { + final Character nextCharacter = nextCharacter(); + + sch = "" + c + nextCharacter; + s = Symbol.LATTER; + } else + s = Symbol.LATTER; + + final State newState = transition(state, s); + + if (newState == State.F) + throw new IllegalStateException("Unexpected end of file"); + + State oldState = state; + state = newState; + + if (state != State.E && state != State.S) { + if (state != oldState) { + result.append(stateWord); + stateWord = new StringBuilder(); + } + + if (sch != null) + stateWord.append(sch); + else + stateWord.append(c); + } + + if (state == State.E) { + if (stateWord.length() > 0 && (oldState != State.D)) + result.append(stateWord); + } + } + + return result.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + try { + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Symbol symbol(Character c) { + if (c.equals('\'')) + return Symbol.AP; + if (c.equals('"')) + return Symbol.QT; + if (separators.contains(c)) + return Symbol.SEP; + if (Character.isWhitespace(c)) + return Symbol.WS; + + return Symbol.LATTER; + } + + private State transition(State s, Symbol c) { + switch (s) { + case S: + switch (c) { + case LATTER: + return State.A; + case WS: + return State.S; + case AP: + return State.B; + case QT: + return State.C; + case SEP: + return State.S; + case EOF: + return State.E; + } + break; + case A: + case D: + switch (c) { + case LATTER: + return State.A; + case WS: + return State.D; + case AP: + return State.B; + case QT: + return State.C; + case SEP: + return State.E; + case EOF: + return State.E; + } + break; + case B: + switch (c) { + case LATTER: + return State.B; + case WS: + return State.B; + case AP: + return State.A; + case QT: + return State.B; + case SEP: + return State.B; + case EOF: + return State.F; + } + break; + case C: + switch (c) { + case LATTER: + return State.C; + case WS: + return State.C; + case AP: + return State.C; + case QT: + return State.A; + case SEP: + return State.C; + case EOF: + return State.F; + } + break; + case E: + return State.E; + case F: + return State.F; + } + + throw new IllegalStateException(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/console/ODefaultConsoleReader.java b/core/src/main/java/com/orientechnologies/common/console/ODefaultConsoleReader.java new file mode 100644 index 00000000000..96b8ab1f2c6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/ODefaultConsoleReader.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import com.orientechnologies.common.thread.OSoftThread; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Console reader implementation that uses the Java System.in. + */ +public class ODefaultConsoleReader implements OConsoleReader { + final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + + private static class EraserThread extends OSoftThread { + @Override + protected void execute() throws Exception { + System.out.print("\010*"); + try { + Thread.sleep(1); + } catch (InterruptedException ie) { + // om nom nom + } + } + } + + @Override + public String readLine() { + try { + return reader.readLine(); + } catch (IOException e) { + return null; + } + } + + @Override + public String readPassword() { + if (System.console() == null) + // IDE + return readLine(); + + System.out.print(" "); + + final EraserThread et = new EraserThread(); + et.start(); + + try { + return reader.readLine(); + } catch (IOException e) { + return null; + } finally { + et.sendShutdown(); + } + } + + @Override + public void setConsole(OConsoleApplication console) { + } + + @Override + public int getConsoleWidth() { + return OConsoleReader.FALLBACK_CONSOLE_WIDTH; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/console/OScannerCommandStream.java b/core/src/main/java/com/orientechnologies/common/console/OScannerCommandStream.java new file mode 100755 index 00000000000..b3f2f2f5899 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/OScannerCommandStream.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OScannerCommandStream implements OCommandStream { + private Scanner scanner; + + public OScannerCommandStream(String commands) { + scanner = new Scanner(commands); + init(); + } + + public OScannerCommandStream(File file) throws FileNotFoundException { + scanner = new Scanner(file); + init(); + } + + private void init() { + scanner.useDelimiter(";(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^']*'[^']*')*[^']*$)|\n"); + } + + @Override + public boolean hasNext() { + return scanner.hasNext(); + } + + @Override + public String nextCommand() { + return scanner.next().trim(); + } + + @Override + public void close() { + scanner.close(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/console/TTYConsoleReader.java b/core/src/main/java/com/orientechnologies/common/console/TTYConsoleReader.java new file mode 100755 index 00000000000..21b9ee0dfeb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/TTYConsoleReader.java @@ -0,0 +1,565 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console; + +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.common.log.OLogManager; +import sun.misc.Signal; +import sun.misc.SignalHandler; + +import java.io.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Custom implementation of TTY reader. Supports arrow keys + history. + */ +public class TTYConsoleReader implements OConsoleReader { + private final static String HISTORY_FILE_NAME = ".orientdb_history"; + public final static int END_CHAR = 70; + public final static int BEGIN_CHAR = 72; + public final static int DEL_CHAR = 126; + public final static int DOWN_CHAR = 66; + public final static int UP_CHAR = 65; + public final static int RIGHT_CHAR = 67; + public final static int LEFT_CHAR = 68; + public final static int HORIZONTAL_TAB_CHAR = 9; + public final static int VERTICAL_TAB_CHAR = 11; + public final static int BACKSPACE_CHAR = 127; + public final static int NEW_LINE_CHAR = 10; + public final static int UNIT_SEPARATOR_CHAR = 31; + private final static int MAX_HISTORY_ENTRIES = 50; + + private static final Object signalLock = new Object(); + private static int cachedConsoleWidth = -1; // -1 for no cached value, -2 to indicate the error + + static { + final Signal signal = new Signal("WINCH"); + Signal.handle(signal, new SignalHandler() { + @Override + public void handle(Signal signal) { + synchronized (signalLock) { + cachedConsoleWidth = -1; + } + } + }); + } + + protected int cursorPosition = 0; + protected int oldPromptLength = 0; + protected int oldTextLength = 0; + protected int oldCursorPosition = 0; + protected int maxTotalLength = 0; + + protected final List history = new ArrayList(); + + protected String historyBuffer; + + protected Reader inStream; + + protected PrintStream outStream; + protected OConsoleApplication console; + + public TTYConsoleReader() { + File file = getHistoryFile(true); + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(file)); + String historyEntry = reader.readLine(); + while (historyEntry != null) { + history.add(historyEntry); + historyEntry = reader.readLine(); + } + if (System.getProperty("file.encoding") != null) { + inStream = new InputStreamReader(System.in, System.getProperty("file.encoding")); + outStream = new PrintStream(System.out, false, System.getProperty("file.encoding")); + } else { + inStream = new InputStreamReader(System.in); + outStream = System.out; + } + } catch (FileNotFoundException fnfe) { + OLogManager.instance().error(this, "History file not found", fnfe); + } catch (IOException ioe) { + OLogManager.instance().error(this, "Error reading history file", ioe); + } + + if (inStream == null) + throw new OSystemException("Cannot access to the input stream. Check permissions of running process"); + } + + public String readPassword() throws IOException { + return readLine(); + } + + public String readLine() throws IOException { + String consoleInput = ""; + + StringBuffer buffer = new StringBuffer(); + cursorPosition = 0; + historyBuffer = null; + int historyNum = history.size(); + boolean hintedHistory = false; + while (true) { + boolean escape = false; + boolean ctrl = false; + int next = inStream.read(); + if (next == 27) { + escape = true; + inStream.read(); + next = inStream.read(); + } + if (escape) { + if (next == 49) { + inStream.read(); + next = inStream.read(); + } + if (next == 53) { + ctrl = true; + next = inStream.read(); + } + if (ctrl) { + if (next == RIGHT_CHAR) { + cursorPosition = buffer.indexOf(" ", cursorPosition) + 1; + if (cursorPosition == 0) + cursorPosition = buffer.length(); + updatePrompt(buffer); + } else if (next == LEFT_CHAR) { + if (cursorPosition > 1 && cursorPosition < buffer.length() && buffer.charAt(cursorPosition - 1) == ' ') { + cursorPosition = buffer.lastIndexOf(" ", (cursorPosition - 2)) + 1; + } else { + cursorPosition = buffer.lastIndexOf(" ", cursorPosition) + 1; + } + if (cursorPosition < 0) + cursorPosition = 0; + updatePrompt(buffer); + } + } else { + if (next == UP_CHAR && !history.isEmpty()) { + if (history.size() > 0) { // UP + if (!hintedHistory && (historyNum == history.size() || !buffer.toString().equals(history.get(historyNum)))) { + if (buffer.length() > 0) { + hintedHistory = true; + historyBuffer = buffer.toString(); + } else { + historyBuffer = null; + } + } + historyNum = getHintedHistoryIndexUp(historyNum); + if (historyNum > -1) { + buffer = new StringBuffer(history.get(historyNum)); + } else { + buffer = new StringBuffer(historyBuffer); + } + cursorPosition = buffer.length(); + updatePrompt(buffer); + } + } else if (next == DOWN_CHAR && !history.isEmpty()) { // DOWN + if (history.size() > 0) { + historyNum = getHintedHistoryIndexDown(historyNum); + if (historyNum == history.size()) { + if (historyBuffer != null) { + buffer = new StringBuffer(historyBuffer); + } else { + buffer = new StringBuffer(""); + } + } else { + buffer = new StringBuffer(history.get(historyNum)); + } + cursorPosition = buffer.length(); + updatePrompt(buffer); + } + } else if (next == RIGHT_CHAR) { + if (cursorPosition < buffer.length()) { + cursorPosition++; + updatePrompt(buffer); + } + } else if (next == LEFT_CHAR) { + if (cursorPosition > 0) { + cursorPosition--; + updatePrompt(buffer); + } + } else if (next == END_CHAR) { + cursorPosition = buffer.length(); + updatePrompt(buffer); + } else if (next == BEGIN_CHAR) { + cursorPosition = 0; + updatePrompt(buffer); + } + } + } else { + if (next == NEW_LINE_CHAR) { + outStream.println(); + oldPromptLength = 0; + oldTextLength = 0; + oldCursorPosition = 0; + maxTotalLength = 0; + break; + } else if (next == BACKSPACE_CHAR) { + if (buffer.length() > 0 && cursorPosition > 0) { + buffer.deleteCharAt(cursorPosition - 1); + cursorPosition--; + updatePrompt(buffer); + } + } else if (next == DEL_CHAR) { + if (buffer.length() > 0 && cursorPosition >= 0 && cursorPosition < buffer.length()) { + buffer.deleteCharAt(cursorPosition); + updatePrompt(buffer); + } + } else if (next == HORIZONTAL_TAB_CHAR) { + buffer = writeHint(buffer); + cursorPosition = buffer.length(); + updatePrompt(buffer); + } else { + if ((next > UNIT_SEPARATOR_CHAR && next < BACKSPACE_CHAR) || next > BACKSPACE_CHAR) { + if (cursorPosition == buffer.length()) { + buffer.append((char) next); + } else { + buffer.insert(cursorPosition, (char) next); + } + cursorPosition++; + updatePrompt(buffer); + } else { + outStream.println(); + outStream.print(buffer); + } + } + historyNum = history.size(); + hintedHistory = false; + } + } + consoleInput = buffer.toString(); + history.remove(consoleInput); + history.add(consoleInput); + historyNum = history.size(); + writeHistory(historyNum); + + if (consoleInput.equals("clear")) { + outStream.flush(); + for (int i = 0; i < 150; i++) { + outStream.println(); + } + outStream.print("\r"); + outStream.print(console.getPrompt()); + return readLine(); + } else { + return consoleInput; + } + } + + public void setConsole(OConsoleApplication iConsole) { + console = iConsole; + } + + @Override + public int getConsoleWidth() { + synchronized (signalLock) { + if (cachedConsoleWidth > 0) // we have cached value + return cachedConsoleWidth; + + if (cachedConsoleWidth == -1) { // no cached value + try { + final Process process = Runtime.getRuntime().exec(new String[] { "sh", "-c", "tput cols 2> /dev/tty" }); + final String line = new BufferedReader(new InputStreamReader(process.getInputStream())).readLine(); + process.waitFor(); + + if (process.exitValue() == 0 && line != null) { + cachedConsoleWidth = Integer.parseInt(line); + } else + cachedConsoleWidth = -2; + } catch (IOException e) { + cachedConsoleWidth = -2; + } catch (InterruptedException e) { + cachedConsoleWidth = -2; + } catch (NumberFormatException e) { + cachedConsoleWidth = -2; + } + } + + return cachedConsoleWidth == -2 || cachedConsoleWidth == 0 ? OConsoleReader.FALLBACK_CONSOLE_WIDTH : cachedConsoleWidth; + } + } + + private void writeHistory(int historyNum) throws IOException { + if (historyNum <= MAX_HISTORY_ENTRIES) { + File historyFile = getHistoryFile(false); + BufferedWriter writer = new BufferedWriter(new FileWriter(historyFile)); + try { + for (String historyEntry : history) { + writer.write(historyEntry); + writer.newLine(); + } + } finally { + writer.flush(); + writer.close(); + } + } else { + File historyFile = getHistoryFile(false); + BufferedWriter writer = new BufferedWriter(new FileWriter(historyFile)); + try { + for (String historyEntry : history.subList(historyNum - MAX_HISTORY_ENTRIES - 1, historyNum - 1)) { + writer.write(historyEntry); + writer.newLine(); + } + } finally { + writer.flush(); + writer.close(); + } + } + } + + private StringBuffer writeHint(StringBuffer buffer) { + List suggestions = new ArrayList(); + for (Method method : console.getConsoleMethods().keySet()) { + String command = OConsoleApplication.getClearName(method.getName()); + if (command.startsWith(buffer.toString())) { + suggestions.add(command); + } + } + if (suggestions.size() > 1) { + StringBuffer hintBuffer = new StringBuffer(); + String[] bufferComponents = buffer.toString().split(" "); + String[] suggestionComponents; + Set bufferPart = new HashSet(); + String suggestionPart = null; + boolean appendSpace = true; + for (String suggestion : suggestions) { + suggestionComponents = suggestion.split(" "); + hintBuffer.append("* " + suggestion + " "); + hintBuffer.append("\n"); + suggestionPart = ""; + if (bufferComponents.length == 0 || buffer.length() == 0) { + suggestionPart = null; + } else if (bufferComponents.length == 1) { + bufferPart.add(suggestionComponents[0]); + if (bufferPart.size() > 1) { + suggestionPart = bufferComponents[0]; + appendSpace = false; + } else { + suggestionPart = suggestionComponents[0]; + } + } else { + bufferPart.add(suggestionComponents[bufferComponents.length - 1]); + if (bufferPart.size() > 1) { + for (int i = 0; i < bufferComponents.length; i++) { + suggestionPart += bufferComponents[i]; + if (i < (bufferComponents.length - 1)) { + suggestionPart += " "; + } + appendSpace = false; + } + } else { + for (int i = 0; i < suggestionComponents.length; i++) { + suggestionPart += suggestionComponents[i] + " "; + } + } + } + } + if (suggestionPart != null) { + buffer = new StringBuffer(); + buffer.append(suggestionPart); + if (appendSpace) { + buffer.append(" "); + } + } + hintBuffer.append("-----------------------------\n"); + updateHints(hintBuffer); + } else if (suggestions.size() > 0) { + buffer = new StringBuffer(); + buffer.append(suggestions.get(0)); + buffer.append(" "); + } + return buffer; + } + + private void updatePrompt(StringBuffer newText) { + final int consoleWidth = getConsoleWidth(); + final String newPrompt = console.getPrompt(); + final int newTotalLength = newPrompt.length() + newText.length(); + final int oldTotalLength = oldPromptLength + oldTextLength; + + // 1. Hide the cursor to reduce flickering. + + hideCursor(); + + // 2. Position to the old prompt beginning. + + outStream.print("\r"); + moveCursorVertically(-getWraps(oldPromptLength, oldCursorPosition, consoleWidth)); + + // 3. Write the new prompt over the old one. + + outStream.print(newPrompt); + outStream.print(newText); + + // 4. Clear the remaining ending part of the old prompt, if any. + + final StringBuilder spaces = new StringBuilder(); + final int promptLengthDelta = oldTotalLength - newTotalLength; + for (int i = 0; i < promptLengthDelta; i++) + spaces.append(' '); + outStream.print(spaces); + + // 5. Reset the cursor to the prompt beginning. We may do navigation relative to the updated console cursor position, + // but the math becomes too tricky in this case. Feel free to fix this ;) + + outStream.print("\r"); + if (newTotalLength > oldTotalLength) { + if (newTotalLength > maxTotalLength && newTotalLength % consoleWidth == 0) { + outStream.print('\n'); // make room for the cursor + moveCursorVertically(-1); + } + moveCursorVertically(-getWraps(newPrompt.length(), newText.length() - 1, consoleWidth)); + } else + moveCursorVertically(-getWraps(oldPromptLength, oldTextLength - 1, consoleWidth)); + + // 6. Place the cursor at the right place. + + moveCursorVertically((newPrompt.length() + cursorPosition) / consoleWidth); + moveCursorHorizontally((newPrompt.length() + cursorPosition) % consoleWidth); + + // 7. Update the stored things. + + oldPromptLength = newPrompt.length(); + oldTextLength = newText.length(); + oldCursorPosition = cursorPosition; + maxTotalLength = Math.max(newTotalLength, maxTotalLength); + + // 8. Show the cursor. + + showCursor(); + } + + private void erasePrompt() { + final int consoleWidth = getConsoleWidth(); + final int oldTotalLength = oldPromptLength + oldTextLength; + + // 1. Hide the cursor to reduce flickering. + + hideCursor(); + + // 2. Position to the prompt beginning. + + outStream.print("\r"); + moveCursorVertically(-getWraps(oldPromptLength, oldCursorPosition, consoleWidth)); + + // 3. Clear the prompt. + + final StringBuilder spaces = new StringBuilder(); + for (int i = 0; i < oldTotalLength; i++) + spaces.append(' '); + outStream.print(spaces); + + // 4. Reset the cursor to the prompt beginning. + + outStream.print("\r"); + moveCursorVertically(-getWraps(oldPromptLength, oldCursorPosition, consoleWidth)); + + // 5. Update the stored things. + + oldPromptLength = 0; + oldTextLength = 0; + oldCursorPosition = 0; + maxTotalLength = 0; + + // 6. Show the cursor. + + showCursor(); + } + + private void updateHints(StringBuffer hints) { + erasePrompt(); + outStream.print("\r"); + outStream.print(hints); + } + + private int getWraps(int promptLength, int cursorPosition, int consoleWidth) { + return (promptLength + Math.max(0, cursorPosition)) / consoleWidth; + } + + private void hideCursor() { + outStream.print("\033[?25l"); + } + + private void showCursor() { + outStream.print("\033[?25h"); + } + + private void moveCursorHorizontally(int delta) { + if (delta > 0) + outStream.print("\033[" + delta + "C"); + else if (delta < 0) + outStream.print("\033[" + Math.abs(delta) + "D"); + } + + private void moveCursorVertically(int delta) { + if (delta > 0) + outStream.print("\033[" + delta + "B"); + else if (delta < 0) + outStream.print("\033[" + Math.abs(delta) + "A"); + } + + private int getHintedHistoryIndexUp(int historyNum) { + if (historyBuffer != null && !historyBuffer.equals("")) { + for (int i = (historyNum - 1); i >= 0; i--) { + if (history.get(i).startsWith(historyBuffer)) { + return i; + } + } + return -1; + } + return historyNum > 0 ? (historyNum - 1) : 0; + } + + private int getHintedHistoryIndexDown(int historyNum) throws IOException { + if (historyBuffer != null && !historyBuffer.equals("")) { + for (int i = historyNum + 1; i < history.size(); i++) { + if (history.get(i).startsWith(historyBuffer)) { + return i; + } + } + return history.size(); + } + return historyNum < history.size() ? (historyNum + 1) : history.size(); + } + + private File getHistoryFile(boolean read) { + File file = new File(HISTORY_FILE_NAME); + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException ioe) { + OLogManager.instance().error(this, "Error creating history file", ioe); + } + } else if (!read) { + file.delete(); + try { + file.createNewFile(); + } catch (IOException ioe) { + OLogManager.instance().error(this, "Error creating history file", ioe); + } + } + return file; + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleCommand.java b/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleCommand.java new file mode 100644 index 00000000000..a135e8c264a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleCommand.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ConsoleCommand { + String[] aliases() default {}; + + String description() default ""; + + boolean splitInWords() default true; + + int priority() default 10; + + String onlineHelp() default ""; +} diff --git a/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleParameter.java b/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleParameter.java new file mode 100644 index 00000000000..1dbc09ff19c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/console/annotation/ConsoleParameter.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.console.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConsoleParameter { + String name() default ""; + + String description() default ""; + + boolean optional() default false; +} diff --git a/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPool.java b/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPool.java new file mode 100755 index 00000000000..0e478074a22 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPool.java @@ -0,0 +1,738 @@ +/* + * + * * Copyright 2015 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.common.directmemory; + +import com.orientechnologies.common.concur.lock.OInterruptedException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import sun.misc.Cleaner; +import sun.nio.ch.DirectBuffer; + +import javax.management.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.management.ManagementFactory; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.LogManager; + +/** + * Object of this class works at the same time as factory for DirectByteBuffer objects and pool for + * DirectByteBuffer objects which were used and now are free to be reused by other parts of the code. + *

+ * All DirectByteBuffer objects have the same size which is specified in objects constructor as "page size". Despite of + * the fact that size of page is relatively small memory may be acquired from OS in relatively big chunks. It is done to optimize + * memory usage inside of database. + * + * @see OGlobalConfiguration#MEMORY_CHUNK_SIZE + */ +public class OByteBufferPool implements OOrientStartupListener, OOrientShutdownListener, OByteBufferPoolMXBean { + /** + * {@link OByteBufferPool}'s MBean name. + */ + public static final String MBEAN_NAME = "com.orientechnologies.common.directmemory:type=OByteBufferPoolMXBean"; + + /** + * Pool returned by this method is used in all components of storage. Memory used by this pool is preallocated by chunks with size + * not more than {@link OGlobalConfiguration#MEMORY_CHUNK_SIZE} Amount of maximum memory preallocated by this pool equals to sum + * of {@link OGlobalConfiguration#DISK_CACHE_SIZE} and {@link OGlobalConfiguration#WAL_CACHE_SIZE} and one. + *

+ * Size of single page equals to {@link OGlobalConfiguration#DISK_CACHE_PAGE_SIZE}. + * + * @return Global instance is used inside of storage. + */ + public static OByteBufferPool instance() { + return InstanceHolder.INSTANCE; + } + + private static final boolean TRACK = OGlobalConfiguration.DIRECT_MEMORY_TRACK_MODE.getValueAsBoolean(); + + /** + * Size of single byte buffer instance in bytes. + */ + private final int pageSize; + + /** + * Map which contains collection of preallocated chunks and their indexes. Indexes are numbered in order of their allocation. + */ + private final ConcurrentHashMap preallocatedAreas = new ConcurrentHashMap(); + + /** + * Index of next page which will be allocated if pool is empty. + */ + private final AtomicLong nextAllocationPosition = new AtomicLong(); + + /** + * Maximum amount of pages which should be allocated in single preallocated memory chunk. + */ + private final int maxPagesPerSingleArea; + + /** + * Limit of memory which will be allocated by big chunks (in pages) + */ + private final long preAllocationLimit; + + /** + * Pool of pages which are already allocated but not used any more. + */ + private final ConcurrentLinkedQueue pool = new ConcurrentLinkedQueue(); + + /** + * Tracks the number of the overflow buffer allocations. + */ + private final AtomicLong overflowBufferCount = new AtomicLong(); + + /** + * Tracks the status of the MBean registration. + */ + private final AtomicBoolean mbeanIsRegistered = new AtomicBoolean(); + + /** + * Size of page pool, we use separate counter because {@link ConcurrentLinkedQueue#size()} has linear complexity. + */ + private final AtomicInteger poolSize = new AtomicInteger(); + + /** + * Amount of native memory in bytes consumed by current byte buffer pool + */ + private final AtomicLong allocatedMemory = new AtomicLong(); + + private final ReferenceQueue trackedBuffersQueue; + private final Set trackedReferences; + private final Map trackedBuffers; + private final Map trackedReleases; + + /** + * @param pageSize Size of single page (instance of DirectByteBuffer) returned by pool. + */ + public OByteBufferPool(int pageSize) { + this(pageSize, -1, -1); + } + + /** + * @param pageSize Size of single page (DirectByteBuffer) returned by pool. + * @param maxChunkSize Maximum allocation chunk size + * @param preAllocationLimit Limit of memory which will be allocated by big chunks + */ + public OByteBufferPool(int pageSize, int maxChunkSize, long preAllocationLimit) { + this.pageSize = pageSize; + + this.preAllocationLimit = (preAllocationLimit / pageSize) * pageSize; + + int pagesPerArea = (maxChunkSize / pageSize); + if (pagesPerArea > 1) { + pagesPerArea = closestPowerOfTwo(pagesPerArea); + + // we need not the biggest value, it may cause buffer overflow, but biggest after that. + while ((long) pagesPerArea * pageSize >= maxChunkSize) { + pagesPerArea = pagesPerArea >>> 1; + } + + maxPagesPerSingleArea = pagesPerArea; + } else { + maxPagesPerSingleArea = 1; + } + + if (TRACK) { + trackedBuffersQueue = new ReferenceQueue(); + trackedReferences = new HashSet(); + trackedBuffers = new HashMap(); + trackedReleases = new HashMap(); + } else { + trackedBuffersQueue = null; + trackedReferences = null; + trackedBuffers = null; + trackedReleases = null; + } + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + /** + * @return Amount of pages which are available in pool. Pages which were allocated and now not used. + */ + public int getSize() { + return pool.size(); + } + + /** + * @return Maximum amount of pages in single preallocate memory chunk. + */ + public int getMaxPagesPerChunk() { + return maxPagesPerSingleArea; + } + + /** + * Finds closest power of two for given integer value. Idea is simple duplicate the most significant bit to the lowest bits for + * the smallest number of iterations possible and then increment result value by 1. + * + * @param value Integer the most significant power of 2 should be found. + * + * @return The most significant power of 2. + */ + private int closestPowerOfTwo(int value) { + int n = value - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= (1 << 30)) ? 1 << 30 : n + 1; + } + + /** + * Acquires direct memory buffer. If there is free (already released) direct memory buffer we reuse it, otherwise either new + * memory chunk is allocated from direct memory or slice of already preallocated memory chunk is used as new byte buffer instance. + *

+ * If we reached maximum amount of preallocated memory chunks then small portion of direct memory equals to page size is + * allocated. Byte order of returned direct memory buffer equals to native byte order. + *

+ * Position of returned buffer is always zero. + * + * @param clear Whether returned buffer should be filled with zeros before return. + * + * @return Direct memory buffer instance. + */ + public ByteBuffer acquireDirect(boolean clear) { + // check the pool first. + final ByteBuffer buffer = pool.poll(); + + if (buffer != null) { + poolSize.decrementAndGet(); + + if (clear) { + buffer.position(0); + buffer.put(new byte[pageSize]); + } + + buffer.position(0); + + return trackBuffer(buffer); + } + + if (maxPagesPerSingleArea > 1) { + long currentAllocationPosition; + + do { + currentAllocationPosition = nextAllocationPosition.get(); + + //if we hit the end of preallocation buffer we allocate by small chunks + if (currentAllocationPosition >= preAllocationLimit) { + overflowBufferCount.incrementAndGet(); + allocatedMemory.getAndAdd(pageSize); + + return trackBuffer(ByteBuffer.allocateDirect(pageSize).order(ByteOrder.nativeOrder())); + } + + } while (!nextAllocationPosition.compareAndSet(currentAllocationPosition, currentAllocationPosition + 1)); + + //all chucks consumes maxPagesPerSingleArea space with exception of last one + int position = (int) (currentAllocationPosition & (maxPagesPerSingleArea - 1)); + int bufferIndex = (int) (currentAllocationPosition / maxPagesPerSingleArea); + + //allocation size should be the same for all buffers from chuck with the same index + final int allocationSize = (int) Math + .min(maxPagesPerSingleArea * pageSize, (preAllocationLimit - bufferIndex * maxPagesPerSingleArea) * pageSize); + + // we cannot free chunk of allocated memory so we set place holder first + // if operation successful we allocate part of direct memory. + BufferHolder bfh = preallocatedAreas.get(bufferIndex); + + if (bfh == null) { + bfh = new BufferHolder(); + + BufferHolder replacedBufferHolder = preallocatedAreas.putIfAbsent(bufferIndex, bfh); + + if (replacedBufferHolder == null) { + allocateBuffer(bfh, allocationSize); + } else { + bfh = replacedBufferHolder; + } + } + + if (bfh.buffer == null) { + // if place holder is not null it means that byte buffer is allocated but not set yet in other thread + // so we wait till buffer instance will be shared by other thread + try { + bfh.latch.await(); + } catch (InterruptedException e) { + throw OException.wrapException(new OInterruptedException("Wait of new preallocated memory area was interrupted"), e); + } + } + + final int rawPosition = position * pageSize; + // duplicate buffer to have thread local version of buffer position. + final ByteBuffer db = bfh.buffer.duplicate(); + + db.position(rawPosition); + db.limit(rawPosition + pageSize); + + ByteBuffer slice = db.slice(); + slice.order(ByteOrder.nativeOrder()); + + if (clear) { + slice.position(0); + slice.put(new byte[pageSize]); + } + + slice.position(0); + return trackBuffer(slice); + } + + // this should not happen if amount of pages is needed for storage is calculated correctly + overflowBufferCount.incrementAndGet(); + allocatedMemory.getAndAdd(pageSize); + + return trackBuffer(ByteBuffer.allocateDirect(pageSize).order(ByteOrder.nativeOrder())); + } + + /** + * Allocates direct byte buffer for buffer holder and notifies other threads that it can be used. + * + * @param bfh Buffer holder for direct memory buffer to be allocated. + */ + private void allocateBuffer(BufferHolder bfh, int allocationSize) { + try { + bfh.buffer = ByteBuffer.allocateDirect(allocationSize).order(ByteOrder.nativeOrder()); + + allocatedMemory.getAndAdd(allocationSize); + } finally { + bfh.latch.countDown(); + } + } + + /** + * Put buffer which is not used any more back to the pool. + * + * @param buffer Not used instance of buffer. + */ + public void release(ByteBuffer buffer) { + pool.offer(untrackBuffer(buffer)); + + poolSize.incrementAndGet(); + } + + @Override + public int getBufferSize() { + return pageSize; + } + + @Override + public long getPreAllocatedBufferCount() { + return nextAllocationPosition.get(); + } + + @Override + public long getOverflowBufferCount() { + return overflowBufferCount.get(); + } + + @Override + public int getBuffersInThePool() { + return getSize(); + } + + @Override + public long getAllocatedMemory() { + return allocatedMemory.get(); + } + + @Override + public long getAllocatedMemoryInMB() { + return getAllocatedMemory() / (1024 * 1024); + } + + @Override + public double getAllocatedMemoryInGB() { + return Math.ceil((getAllocatedMemory() * 100) / (1024.0 * 1024 * 1024)) / 100; + } + + @Override + public long getPreAllocationLimit() { + return preAllocationLimit; + } + + @Override + public int getMaxPagesPerSingleArea() { + return maxPagesPerSingleArea; + } + + @Override + public int getPoolSize() { + return poolSize.get(); + } + + /** + * Registers the MBean for this byte buffer pool. + * + * @see OByteBufferPoolMXBean + */ + public void registerMBean() { + if (mbeanIsRegistered.compareAndSet(false, true)) { + try { + final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + final ObjectName mbeanName = new ObjectName(MBEAN_NAME); + + if (!server.isRegistered(mbeanName)) { + server.registerMBean(this, mbeanName); + } else { + mbeanIsRegistered.set(false); + OLogManager.instance().warn(this, + "MBean with name %s has already registered. Probably your system was not shutdown correctly" + + " or you have several running applications which use OrientDB engine inside", mbeanName.getCanonicalName()); + } + + } catch (MalformedObjectNameException e) { + throw OException.wrapException(new OSystemException("Error during registration of byte buffer pool MBean"), e); + } catch (InstanceAlreadyExistsException e) { + throw OException.wrapException(new OSystemException("Error during registration of byte buffer pool MBean"), e); + } catch (MBeanRegistrationException e) { + throw OException.wrapException(new OSystemException("Error during registration of byte buffer pool MBean"), e); + } catch (NotCompliantMBeanException e) { + throw OException.wrapException(new OSystemException("Error during registration of byte buffer pool MBean"), e); + } + } + } + + /** + * Unregisters the MBean for this byte buffer pool. + * + * @see OByteBufferPoolMXBean + */ + public void unregisterMBean() { + if (mbeanIsRegistered.compareAndSet(true, false)) { + try { + final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + final ObjectName mbeanName = new ObjectName(MBEAN_NAME); + server.unregisterMBean(mbeanName); + } catch (MalformedObjectNameException e) { + throw OException.wrapException(new OSystemException("Error during unregistration of byte buffer pool MBean"), e); + } catch (InstanceNotFoundException e) { + throw OException.wrapException(new OSystemException("Error during unregistration of byte buffer pool MBean"), e); + } catch (MBeanRegistrationException e) { + throw OException.wrapException(new OSystemException("Error during unregistration of byte buffer pool MBean"), e); + } + } + } + + /** + * Verifies the state of this byte buffer pool for a consistency, leaks, etc. {@link OGlobalConfiguration#DIRECT_MEMORY_TRACK_MODE} + * must be on, otherwise this method does nothing. Detected problems will be printed to the error log. If assertions are on and + * erroneous state is detected, verification will fail with {@link AssertionError} exception. + */ + public void verifyState() { + if (TRACK) { + synchronized (this) { + final boolean logsInAssertions = logInAssertion(); + final StringBuilder builder = logsInAssertions ? new StringBuilder() : null; + + for (TrackedBufferReference reference : trackedReferences) + log(builder, this, "DIRECT-TRACK: unreleased direct memory buffer `%X` detected.", reference.stackTrace, reference.id); + + checkTrackedBuffersLeaks(builder); + + if (logsInAssertions) { + if (builder.length() > 0) + throw new AssertionError(builder.toString()); + } else + assert trackedReferences.size() == 0; + } + } + } + + /** + * Logs all known tracking information about the given buffer. {@link OGlobalConfiguration#DIRECT_MEMORY_TRACK_MODE} must be on, + * otherwise this method does nothing. + * + * @param prefix the log message prefix + * @param buffer the buffer to print information about + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public void logTrackedBufferInfo(String prefix, ByteBuffer buffer) { + if (TRACK) { + synchronized (this) { + final TrackedBufferKey trackedBufferKey = new TrackedBufferKey(buffer); + final TrackedBufferReference reference = trackedBuffers.get(trackedBufferKey); + + final StringBuilder builder = new StringBuilder(); + builder.append("DIRECT-TRACK: ").append(prefix).append(String.format(" buffer `%X` ", id(buffer))); + + if (reference == null) + builder.append("untracked"); + else + builder.append("allocated from: ").append('\n').append(getStackTraceAsString(reference.stackTrace)).append('\n'); + + final Exception release = trackedReleases.get(trackedBufferKey); + if (release != null) + builder.append("released from: ").append('\n').append(getStackTraceAsString(release)).append('\n'); + + OLogManager.instance().error(this, builder.toString()); + } + } + } + + @Override + public void onStartup() { + } + + @Override + public void onShutdown() { + final Set cleaned = Collections.newSetFromMap(new IdentityHashMap()); + try { + for (ByteBuffer byteBuffer : pool) + clean(byteBuffer, cleaned); + } catch (Throwable t) { + return; + } + + if (this.preallocatedAreas != null) { + for (BufferHolder bufferHolder : preallocatedAreas.values()) { + clean(bufferHolder.buffer, cleaned); + } + this.preallocatedAreas.clear(); + } + + nextAllocationPosition.set(0); + pool.clear(); + overflowBufferCount.set(0); + poolSize.set(0); + allocatedMemory.set(0); + + if (TRACK) { + for (TrackedBufferReference reference : trackedReferences) + reference.clear(); + trackedReferences.clear(); + trackedBuffers.clear(); + trackedReleases.clear(); + + //noinspection StatementWithEmptyBody + while (trackedBuffersQueue.poll() != null) + ; + } + } + + private void clean(ByteBuffer buffer, Set cleaned) { + final ByteBuffer directByteBufferWithCleaner = findDirectByteBufferWithCleaner(buffer, 16); + if (directByteBufferWithCleaner != null && !cleaned.contains(directByteBufferWithCleaner)) { + cleaned.add(directByteBufferWithCleaner); + ((DirectBuffer) directByteBufferWithCleaner).cleaner().clean(); + if (TRACK) + OLogManager.instance().info(this, "DIRECT-TRACK: cleaned " + directByteBufferWithCleaner); + } + } + + private static ByteBuffer findDirectByteBufferWithCleaner(ByteBuffer buffer, int depthLimit) { + if (depthLimit == 0) + return null; + + if (!(buffer instanceof DirectBuffer)) + return null; + final DirectBuffer directBuffer = (DirectBuffer) buffer; + + final Cleaner cleaner = directBuffer.cleaner(); + if (cleaner != null) + return buffer; + + final Object attachment = directBuffer.attachment(); + if (!(attachment instanceof ByteBuffer)) + return null; + + return findDirectByteBufferWithCleaner((ByteBuffer) attachment, depthLimit - 1); + } + + private static final class BufferHolder { + private volatile ByteBuffer buffer; + private final CountDownLatch latch = new CountDownLatch(1); + } + + private ByteBuffer trackBuffer(ByteBuffer buffer) { + if (TRACK) { + synchronized (this) { + final boolean logInAssertion = logInAssertion(); + final StringBuilder logBuilder = logInAssertion ? new StringBuilder() : null; + + final TrackedBufferReference reference = new TrackedBufferReference(buffer, trackedBuffersQueue); + trackedReferences.add(reference); + trackedBuffers.put(new TrackedBufferKey(buffer), reference); + + checkTrackedBuffersLeaks(logBuilder); + + if (logInAssertion && logBuilder.length() > 0) + throw new AssertionError(logBuilder.toString()); + } + } + + return buffer; + } + + @SuppressWarnings({ "ThrowableResultOfMethodCallIgnored" }) + private ByteBuffer untrackBuffer(ByteBuffer buffer) { + if (TRACK) { + synchronized (this) { + final boolean logInAssertion = logInAssertion(); + final StringBuilder logBuilder = logInAssertion ? new StringBuilder() : null; + + final TrackedBufferKey trackedBufferKey = new TrackedBufferKey(buffer); + + final TrackedBufferReference reference = trackedBuffers.remove(trackedBufferKey); + if (reference == null) { + log(logBuilder, this, "DIRECT-TRACK: untracked direct byte buffer `%X` detected.", new Exception(), id(buffer)); + + final Exception lastRelease = trackedReleases.get(trackedBufferKey); + if (lastRelease != null) + log(logBuilder, this, "DIRECT-TRACK: last release.", lastRelease); + + if (logInAssertion) { + if (logBuilder.length() > 0) + throw new AssertionError(logBuilder.toString()); + } else + assert false; + } else + trackedReferences.remove(reference); + + trackedReleases.put(trackedBufferKey, new Exception()); + + checkTrackedBuffersLeaks(logBuilder); + if (logInAssertion && logBuilder.length() > 0) + throw new AssertionError(logBuilder.toString()); + } + } + + return buffer; + } + + private void checkTrackedBuffersLeaks(StringBuilder logBuilder) { + boolean leaked = false; + + TrackedBufferReference reference; + while ((reference = (TrackedBufferReference) trackedBuffersQueue.poll()) != null) { + if (trackedReferences.remove(reference)) { + log(logBuilder, this, "DIRECT-TRACK: unreleased direct byte buffer `%X` detected.", reference.stackTrace, reference.id); + leaked = true; + } + } + + if (logBuilder == null) + assert !leaked; + } + + private static int id(Object object) { + return System.identityHashCode(object); + } + + @SuppressWarnings({ "AssertWithSideEffects", "ConstantConditions" }) + private static boolean logInAssertion() { + boolean assertionsEnabled = false; + assert assertionsEnabled = true; + return assertionsEnabled && !(LogManager.getLogManager() instanceof OLogManager.DebugLogManager); + } + + private static void log(StringBuilder builder, final Object from, final String message, final Throwable exception, + final Object... args) { + if (builder == null) + OLogManager.instance().error(from, message, exception, args); + else { + final String newLine = String.format("%n"); + builder.append(String.format(message, args)).append(newLine); + if (exception != null) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(stringWriter); + exception.printStackTrace(printWriter); + builder.append(stringWriter.toString()); + } + } + } + + private static String getStackTraceAsString(Throwable throwable) { + final StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + private static class TrackedBufferReference extends WeakReference { + + public final int id; + public final Exception stackTrace; + + public TrackedBufferReference(ByteBuffer referent, ReferenceQueue q) { + super(referent, q); + + this.id = id(referent); + this.stackTrace = new Exception(); + } + + } + + private static class TrackedBufferKey extends WeakReference { + + private final int hashCode; + + public TrackedBufferKey(ByteBuffer referent) { + super(referent); + hashCode = System.identityHashCode(referent); + } + + @Override + public int hashCode() { + return hashCode; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object obj) { + final ByteBuffer buffer = get(); + return buffer != null && buffer == ((TrackedBufferKey) obj).get(); + } + + } + + private static class InstanceHolder { + private static final OByteBufferPool INSTANCE; + + static { + // page size in bytes + final int pageSize = OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024; + + // Maximum amount of chunk size which should be allocated at once by system + final int memoryChunkSize = OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsInteger(); + + final long diskCacheSize = OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsInteger() * 1024L * 1024L; + + // instance of byte buffer which should be used by all storage components + INSTANCE = new OByteBufferPool(pageSize, memoryChunkSize, diskCacheSize); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPoolMXBean.java b/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPoolMXBean.java new file mode 100755 index 00000000000..6913b1cb6bf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/directmemory/OByteBufferPoolMXBean.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 OrientDB LTD (info(at)orientdb.com) + * + * Licensed 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. + * + * For more information: http://www.orientdb.com + */ + +package com.orientechnologies.common.directmemory; + +/** + * Provides an MBean for {@link OByteBufferPool}. + * + * @author Sergey Sitnikov + */ +public interface OByteBufferPoolMXBean { + + /** + * @return the buffer AKA page size in bytes of the associated {@link OByteBufferPool}. + */ + int getBufferSize(); + + /** + * @return the number of the free buffers currently in the pool of the associated {@link OByteBufferPool}. + */ + int getBuffersInThePool(); + + /** + * @return the number of the allocated buffers of the associated {@link OByteBufferPool}, + * this does not include the overflow buffers. + */ + long getPreAllocatedBufferCount(); + + /** + * @return the number of the allocated overflow buffers of the associated {@link OByteBufferPool}. + */ + long getOverflowBufferCount(); + + /** + * @return the current total memory allocation size in bytes of the the associated {@link OByteBufferPool}, + * including the overflow buffer allocations. + */ + long getAllocatedMemory(); + + /** + * @return the current total memory allocation size in megabytes of the the associated {@link OByteBufferPool}, + * including the overflow buffer allocations. + */ + long getAllocatedMemoryInMB(); + + /** + * @return the current total memory allocation size in gigabytes of the the associated {@link OByteBufferPool}, + * including the overflow buffer allocations. + */ + double getAllocatedMemoryInGB(); + + /** + * @return Maximum amount of bytes which may be allocated using big chunks of memory + */ + long getPreAllocationLimit(); + + /** + * @return Amount of pages which fit in single big chunk of memory preallocated during page allocation request. + */ + int getMaxPagesPerSingleArea(); + + /** + * @return Size of the pool which contains records which were allocated but now were fried to the pool by database. + */ + int getPoolSize(); + +} diff --git a/core/src/main/java/com/orientechnologies/common/exception/OErrorCategory.java b/core/src/main/java/com/orientechnologies/common/exception/OErrorCategory.java new file mode 100755 index 00000000000..d50e9f3cce0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/exception/OErrorCategory.java @@ -0,0 +1,20 @@ +package com.orientechnologies.common.exception; + +/** + * Created by luigidellaquila on 13/08/15. + */ +public enum OErrorCategory { + + SQL_GENERIC(1), + + SQL_PARSING(2), + + STORAGE(3); + + protected final int code; + + private OErrorCategory(int code) { + this.code = code; + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/exception/OErrorCode.java b/core/src/main/java/com/orientechnologies/common/exception/OErrorCode.java new file mode 100755 index 00000000000..2dfb4f87829 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/exception/OErrorCode.java @@ -0,0 +1,81 @@ +package com.orientechnologies.common.exception; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OApi; +import com.orientechnologies.orient.core.exception.OBackupInProgressException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; + +import java.lang.reflect.InvocationTargetException; + +/** + * Enumeration with the error managed by OrientDB. This class has been introduced in v.2.2 and little by little will contain all the + * OrientDB managed errors. + * + * @author Luigi Dell'Aquila + */ +@OApi(maturity = OApi.MATURITY.NEW) +public enum OErrorCode { + + // eg. + QUERY_PARSE_ERROR(OErrorCategory.SQL_PARSING, 1, "query parse error", OQueryParsingException.class), BACKUP_IN_PROGRESS( + OErrorCategory.STORAGE, 2, "You are trying to start a backup, but it is already in progress", + OBackupInProgressException.class); + + protected final OErrorCategory category; + protected final int code; + protected final String description; + protected final Class exceptionClass; + + OErrorCode(OErrorCategory category, int code, String description) { + this(category, code, description, OException.class); + } + + OErrorCode(OErrorCategory category, int code, String description, Class exceptionClass) { + this.category = category; + this.code = code; + this.description = description; + this.exceptionClass = exceptionClass; + } + + public int getCode() { + return code; + } + + public void throwException() { + throwException(this.description, null); + } + + public void throwException(String message) { + throwException(message, null); + } + + public void throwException(Throwable parent) { + throwException(this.description, parent); + } + + public void throwException(String message, Throwable parent) { + final String fullMessage = String.format("%1$06d_%2$06d - %s", category.code, code, message); + try { + OException exc = OException.wrapException(exceptionClass.getConstructor(String.class).newInstance(message), parent); + throw exc; + } catch (InstantiationException e) { + OLogManager.instance().warn(this, "Cannot instantiate exception "+exceptionClass); + e.printStackTrace(); + parent.printStackTrace(); + } catch (IllegalAccessException e) { + OLogManager.instance().warn(this, "Cannot instantiate exception "+exceptionClass); + e.printStackTrace(); + parent.printStackTrace(); + } catch (NoSuchMethodException e) { + OLogManager.instance().warn(this, "Cannot instantiate exception "+exceptionClass); + e.printStackTrace(); + parent.printStackTrace(); + } catch (InvocationTargetException e) { + OLogManager.instance().warn(this, "Cannot instantiate exception "+exceptionClass); + e.printStackTrace(); + parent.printStackTrace(); + } + + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/exception/OException.java b/core/src/main/java/com/orientechnologies/common/exception/OException.java new file mode 100755 index 00000000000..3a2304bd0b9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/exception/OException.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.exception; + +import com.orientechnologies.common.log.OLogManager; + +public abstract class OException extends RuntimeException { + + private static final long serialVersionUID = 3882447822497861424L; + + public static OException wrapException(final OException exception, final Throwable cause) { + if (cause instanceof OHighLevelException) + return (OException) cause; + + exception.initCause(cause); + return exception; + } + + public OException(final String message) { + super(message); + } + + /** + * This constructor is needed to restore and reproduce exception on client side in case of remote storage exception handling. + * Please create "copy constructor" for each exception which has current one as a parent. + */ + public OException(final OException exception) { + super(exception.getMessage(), exception.getCause()); + } + + /** + * Passing of root exceptions directly is prohibited use {@link #wrapException(OException, Throwable)} instead. + */ + private OException(final Throwable cause) { + super(cause); + } + + /** + * Passing of root exceptions directly is prohibited use {@link #wrapException(OException, Throwable)} instead. + */ + private OException(final String message, final Throwable cause) { + super(message, cause); + } + + public static Throwable getFirstCause(Throwable iRootException) { + while (iRootException.getCause() != null) + iRootException = iRootException.getCause(); + + return iRootException; + } + + public static void dumpStackTrace(final String message) { + // DUMP CONTEXT + OLogManager.instance().flush(); + new Exception(message).printStackTrace(); + System.err.flush(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/exception/OHighLevelException.java b/core/src/main/java/com/orientechnologies/common/exception/OHighLevelException.java new file mode 100755 index 00000000000..26f98ff3571 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/exception/OHighLevelException.java @@ -0,0 +1,8 @@ +package com.orientechnologies.common.exception; + +/** + * @author Andrey Lomakin . + * @since 9/28/2015 + */ +public interface OHighLevelException { +} diff --git a/core/src/main/java/com/orientechnologies/common/exception/OSystemException.java b/core/src/main/java/com/orientechnologies/common/exception/OSystemException.java new file mode 100755 index 00000000000..4198964e254 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/exception/OSystemException.java @@ -0,0 +1,16 @@ +package com.orientechnologies.common.exception; + +/** + * @author Andrey Lomakin . + * @since 9/28/2015 + */ +public class OSystemException extends OException { + + public OSystemException(OSystemException exception) { + super(exception); + } + + public OSystemException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatefulFactory.java b/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatefulFactory.java new file mode 100755 index 00000000000..209c3dd19f0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatefulFactory.java @@ -0,0 +1,100 @@ +/* + * + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.common.factory; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Configurable stateful factory. New instances are created when newInstance() is called, invoking its default empty constructor. + * + * @param + * Factory key + * @param + * Instance type + */ +public class OConfigurableStatefulFactory { + protected final Map> registry = new LinkedHashMap>(); + protected Class defaultClass; + + public Class get(final K iKey) { + return registry.get(iKey); + } + + public V newInstance(final K iKey) { + if (iKey == null && defaultClass == null) + throw new IllegalArgumentException("Cannot create implementation for type null"); + + final Class cls = registry.get(iKey); + if (cls != null) { + try { + return cls.newInstance(); + } catch (Exception e) { + final OSystemException exception = new OSystemException(String.format( + "Error on creating new instance of class '%s' registered in factory with key '%s'", cls, iKey)); + throw OException.wrapException(exception, e); + } + } + + return newInstanceOfDefaultClass(); + } + + public V newInstanceOfDefaultClass() { + if (defaultClass != null) { + try { + return defaultClass.newInstance(); + } catch (Exception e) { + throw OException.wrapException( + new OSystemException(String.format("Error on creating new instance of default class '%s'", defaultClass)), e); + } + } + return null; + } + + public Set getRegisteredNames() { + return registry.keySet(); + } + + public OConfigurableStatefulFactory register(final K iKey, final Class iValue) { + registry.put(iKey, iValue); + return this; + } + + public OConfigurableStatefulFactory unregister(final K iKey) { + registry.remove(iKey); + return this; + } + + public OConfigurableStatefulFactory unregisterAll() { + registry.clear(); + return this; + } + + public Class getDefaultClass() { + return defaultClass; + } + + public > OConfigurableStatefulFactory setDefaultClass(final C defaultClass) { + this.defaultClass = defaultClass; + return this; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatelessFactory.java b/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatelessFactory.java new file mode 100755 index 00000000000..5bb62cd0ed3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/factory/OConfigurableStatelessFactory.java @@ -0,0 +1,69 @@ +/* + * + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.common.factory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Configurable stateless factory. The registered instances must not contain any state, so they can be reused even concurrently. + * + * @param + * Factory key + * @param + * Instance type + */ +public class OConfigurableStatelessFactory { + private final Map registry = new HashMap(); + private V defaultImplementation; + + public V getImplementation(final K iKey) { + if (iKey == null) + return defaultImplementation; + return registry.get(iKey); + } + + public Set getRegisteredImplementationNames() { + return registry.keySet(); + } + + public OConfigurableStatelessFactory registerImplementation(final K iKey, final V iValue) { + registry.put(iKey, iValue); + return this; + } + + public OConfigurableStatelessFactory unregisterImplementation(final K iKey) { + registry.remove(iKey); + return this; + } + + public OConfigurableStatelessFactory unregisterAllImplementations() { + registry.clear(); + return this; + } + + public V getDefaultImplementation() { + return defaultImplementation; + } + + public OConfigurableStatelessFactory setDefaultImplementation(final V defaultImpl) { + this.defaultImplementation = defaultImpl; + return this; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/hash/OMurmurHash3.java b/core/src/main/java/com/orientechnologies/common/hash/OMurmurHash3.java new file mode 100644 index 00000000000..d47f981c4a8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/hash/OMurmurHash3.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.hash; + +/** + * @since 13.08.12 + */ +public class OMurmurHash3 { + static class State { + long h1; + long h2; + + long k1; + long k2; + + long c1; + long c2; + } + + static long getblock(byte[] key, int i) { + return (((long) key[i + 0] & 0x00000000000000FFL)) | (((long) key[i + 1] & 0x00000000000000FFL) << 8) + | (((long) key[i + 2] & 0x00000000000000FFL) << 16) | (((long) key[i + 3] & 0x00000000000000FFL) << 24) + | (((long) key[i + 4] & 0x00000000000000FFL) << 32) | (((long) key[i + 5] & 0x00000000000000FFL) << 40) + | (((long) key[i + 6] & 0x00000000000000FFL) << 48) | (((long) key[i + 7] & 0x00000000000000FFL) << 56); + } + + static void bmix(State state) { + state.k1 *= state.c1; + state.k1 = (state.k1 << 23) | (state.k1 >>> 64 - 23); + state.k1 *= state.c2; + state.h1 ^= state.k1; + state.h1 += state.h2; + + state.h2 = (state.h2 << 41) | (state.h2 >>> 64 - 41); + + state.k2 *= state.c2; + state.k2 = (state.k2 << 23) | (state.k2 >>> 64 - 23); + state.k2 *= state.c1; + state.h2 ^= state.k2; + state.h2 += state.h1; + + state.h1 = state.h1 * 3 + 0x52dce729; + state.h2 = state.h2 * 3 + 0x38495ab5; + + state.c1 = state.c1 * 5 + 0x7b7d159c; + state.c2 = state.c2 * 5 + 0x6bce6396; + } + + static long fmix(long k) { + k ^= k >>> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >>> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >>> 33; + + return k; + } + + public static long murmurHash3_x64_64(final byte[] key, final int seed) { + State state = new State(); + + state.h1 = 0x9368e53c2f6af274L ^ seed; + state.h2 = 0x586dcd208f7cd3fdL ^ seed; + + state.c1 = 0x87c37b91114253d5L; + state.c2 = 0x4cf5ad432745937fL; + + for (int i = 0; i < key.length / 16; i++) { + state.k1 = getblock(key, i * 2 * 8); + state.k2 = getblock(key, (i * 2 + 1) * 8); + + bmix(state); + } + + state.k1 = 0; + state.k2 = 0; + + int tail = (key.length >>> 4) << 4; + + switch (key.length & 15) { + case 15: + state.k2 ^= (long) key[tail + 14] << 48; + case 14: + state.k2 ^= (long) key[tail + 13] << 40; + case 13: + state.k2 ^= (long) key[tail + 12] << 32; + case 12: + state.k2 ^= (long) key[tail + 11] << 24; + case 11: + state.k2 ^= (long) key[tail + 10] << 16; + case 10: + state.k2 ^= (long) key[tail + 9] << 8; + case 9: + state.k2 ^= (long) key[tail + 8]; + + case 8: + state.k1 ^= (long) key[tail + 7] << 56; + case 7: + state.k1 ^= (long) key[tail + 6] << 48; + case 6: + state.k1 ^= (long) key[tail + 5] << 40; + case 5: + state.k1 ^= (long) key[tail + 4] << 32; + case 4: + state.k1 ^= (long) key[tail + 3] << 24; + case 3: + state.k1 ^= (long) key[tail + 2] << 16; + case 2: + state.k1 ^= (long) key[tail + 1] << 8; + case 1: + state.k1 ^= (long) key[tail + 0]; + bmix(state); + } + + state.h2 ^= key.length; + + state.h1 += state.h2; + state.h2 += state.h1; + + state.h1 = fmix(state.h1); + state.h2 = fmix(state.h2); + + state.h1 += state.h2; + state.h2 += state.h1; + + return state.h1; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/io/OFileUtils.java b/core/src/main/java/com/orientechnologies/common/io/OFileUtils.java new file mode 100644 index 00000000000..a4b851b2d1d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/io/OFileUtils.java @@ -0,0 +1,215 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; + +public class OFileUtils { + public static final int KILOBYTE = 1024; + public static final int MEGABYTE = 1048576; + public static final int GIGABYTE = 1073741824; + public static final long TERABYTE = 1099511627776L; + + private static final boolean useOldFileAPI; + + static { + boolean oldAPI = false; + + try { + Class.forName("java.nio.file.FileSystemException"); + } catch (ClassNotFoundException e) { + oldAPI = true; + } + + useOldFileAPI = oldAPI; + } + + public static long getSizeAsNumber(final Object iSize) { + if (iSize == null) + throw new IllegalArgumentException("Size is null"); + + if (iSize instanceof Number) + return ((Number) iSize).longValue(); + + String size = iSize.toString(); + + boolean number = true; + for (int i = size.length() - 1; i >= 0; --i) { + final char c = size.charAt(i); + if (!Character.isDigit(c)) { + if (i > 0 || (c != '-' && c != '+')) + number = false; + break; + } + } + + if (number) + return string2number(size).longValue(); + else { + size = size.toUpperCase(Locale.ENGLISH); + int pos = size.indexOf("KB"); + if (pos > -1) + return (long) (string2number(size.substring(0, pos)).floatValue() * KILOBYTE); + + pos = size.indexOf("MB"); + if (pos > -1) + return (long) (string2number(size.substring(0, pos)).floatValue() * MEGABYTE); + + pos = size.indexOf("GB"); + if (pos > -1) + return (long) (string2number(size.substring(0, pos)).floatValue() * GIGABYTE); + + pos = size.indexOf("TB"); + if (pos > -1) + return (long) (string2number(size.substring(0, pos)).floatValue() * TERABYTE); + + pos = size.indexOf('B'); + if (pos > -1) + return (long) string2number(size.substring(0, pos)).floatValue(); + + pos = size.indexOf('%'); + if (pos > -1) + return (long) (-1 * string2number(size.substring(0, pos)).floatValue()); + + // RE-THROW THE EXCEPTION + throw new IllegalArgumentException("Size " + size + " has a unrecognizable format"); + } + } + + public static Number string2number(final String iText) { + if (iText.indexOf('.') > -1) + return Double.parseDouble(iText); + else + return Long.parseLong(iText); + } + + public static String getSizeAsString(final long iSize) { + if (iSize > TERABYTE) + return String.format("%2.2fTB", (float) iSize / TERABYTE); + if (iSize > GIGABYTE) + return String.format("%2.2fGB", (float) iSize / GIGABYTE); + if (iSize > MEGABYTE) + return String.format("%2.2fMB", (float) iSize / MEGABYTE); + if (iSize > KILOBYTE) + return String.format("%2.2fKB", (float) iSize / KILOBYTE); + + return String.valueOf(iSize) + "b"; + } + + public static String getDirectory(String iPath) { + iPath = getPath(iPath); + int pos = iPath.lastIndexOf("/"); + if (pos == -1) + return ""; + + return iPath.substring(0, pos); + } + + public static void createDirectoryTree(final String iFileName) { + final String[] fileDirectories = iFileName.split("/"); + for (int i = 0; i < fileDirectories.length - 1; ++i) + new File(fileDirectories[i]).mkdir(); + } + + public static String getPath(final String iPath) { + if (iPath == null) + return null; + return iPath.replace('\\', '/'); + } + + public static void checkValidName(final String iFileName) throws IOException { + if (iFileName.contains("..") || iFileName.contains("/") || iFileName.contains("\\")) + throw new IOException("Invalid file name '" + iFileName + "'"); + } + + public static void deleteRecursively(final File iRootFile) { + if (iRootFile.exists()) { + if (iRootFile.isDirectory()) { + for (File f : iRootFile.listFiles()) { + if (f.isFile()) + f.delete(); + else + deleteRecursively(f); + } + } + iRootFile.delete(); + } + } + + public static void deleteFolderIfEmpty(final File dir) { + if (dir != null && dir.listFiles() != null && dir.listFiles().length == 0) { + deleteRecursively(dir); + } + } + + @SuppressWarnings("resource") + public static final void copyFile(final File source, final File destination) throws IOException { + FileChannel sourceChannel = new FileInputStream(source).getChannel(); + FileChannel targetChannel = new FileOutputStream(destination).getChannel(); + sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); + sourceChannel.close(); + targetChannel.close(); + } + + public static final void copyDirectory(final File source, final File destination) throws IOException { + if (!destination.exists()) + destination.mkdirs(); + + for (File f : source.listFiles()) { + final File target = new File(destination.getAbsolutePath() + "/" + f.getName()); + if (f.isFile()) + copyFile(f, target); + else + copyDirectory(f, target); + } + } + + public static boolean renameFile(File from, File to) throws IOException { + if (useOldFileAPI) + return from.renameTo(to); + + final FileSystem fileSystem = FileSystems.getDefault(); + + final Path fromPath = fileSystem.getPath(from.getAbsolutePath()); + final Path toPath = fileSystem.getPath(to.getAbsolutePath()); + Files.move(fromPath, toPath); + + return true; + } + + public static boolean delete(File file) throws IOException { + if (!file.exists()) + return true; + + if (useOldFileAPI) + return file.delete(); + + return OFileUtilsJava7.delete(file); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/io/OFileUtilsJava7.java b/core/src/main/java/com/orientechnologies/common/io/OFileUtilsJava7.java new file mode 100644 index 00000000000..e70d4f3e725 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/io/OFileUtilsJava7.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.io; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 7/22/14 + */ +public class OFileUtilsJava7 { + public static boolean delete(File file) throws IOException { + if (!file.exists()) + return true; + + try { + final FileSystem fileSystem = FileSystems.getDefault(); + final Path path = fileSystem.getPath(file.getAbsolutePath()); + + Files.delete(path); + + return true; + } catch (FileSystemException e) { + return false; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/io/OIOException.java b/core/src/main/java/com/orientechnologies/common/io/OIOException.java new file mode 100755 index 00000000000..c1344325cf2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/io/OIOException.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.io; + +import com.orientechnologies.common.exception.OSystemException; + +public class OIOException extends OSystemException { + + private static final long serialVersionUID = -3003977236203691448L; + + public OIOException(OIOException exception) { + super(exception); + } + + public OIOException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/io/OIOUtils.java b/core/src/main/java/com/orientechnologies/common/io/OIOUtils.java new file mode 100755 index 00000000000..1f96cfbc75a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/io/OIOUtils.java @@ -0,0 +1,342 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.io; + +import com.orientechnologies.common.util.OPatternConst; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +public class OIOUtils { + public static final long SECOND = 1000; + public static final long MINUTE = SECOND * 60; + public static final long HOUR = MINUTE * 60; + public static final long DAY = HOUR * 24; + public static final long YEAR = DAY * 365; + public static final long WEEK = DAY * 7; + public static final String UTF8_BOM = "\uFEFF"; + + public static long getTimeAsMillisecs(final Object iSize) { + if (iSize == null) + throw new IllegalArgumentException("Time is null"); + + if (iSize instanceof Number) + // MILLISECS + return ((Number) iSize).longValue(); + + String time = iSize.toString(); + + boolean number = true; + for (int i = time.length() - 1; i >= 0; --i) { + if (!Character.isDigit(time.charAt(i))) { + number = false; + break; + } + } + + if (number) + // MILLISECS + return Long.parseLong(time); + else { + time = time.toUpperCase(Locale.ENGLISH); + + int pos = time.indexOf("MS"); + final String timeAsNumber = OPatternConst.PATTERN_NUMBERS.matcher(time).replaceAll(""); + if (pos > -1) + return Long.parseLong(timeAsNumber); + + pos = time.indexOf("S"); + if (pos > -1) + return Long.parseLong(timeAsNumber) * SECOND; + + pos = time.indexOf("M"); + if (pos > -1) + return Long.parseLong(timeAsNumber) * MINUTE; + + pos = time.indexOf("H"); + if (pos > -1) + return Long.parseLong(timeAsNumber) * HOUR; + + pos = time.indexOf("D"); + if (pos > -1) + return Long.parseLong(timeAsNumber) * DAY; + + pos = time.indexOf('W'); + if (pos > -1) + return Long.parseLong(timeAsNumber) * WEEK; + + pos = time.indexOf('Y'); + if (pos > -1) + return Long.parseLong(timeAsNumber) * YEAR; + + // RE-THROW THE EXCEPTION + throw new IllegalArgumentException("Time '" + time + "' has a unrecognizable format"); + } + } + + public static String getTimeAsString(final long iTime) { + if (iTime > YEAR && iTime % YEAR == 0) + return String.format("%dy", iTime / YEAR); + if (iTime > WEEK && iTime % WEEK == 0) + return String.format("%dw", iTime / WEEK); + if (iTime > DAY && iTime % DAY == 0) + return String.format("%dd", iTime / DAY); + if (iTime > HOUR && iTime % HOUR == 0) + return String.format("%dh", iTime / HOUR); + if (iTime > MINUTE && iTime % MINUTE == 0) + return String.format("%dm", iTime / MINUTE); + if (iTime > SECOND && iTime % SECOND == 0) + return String.format("%ds", iTime / SECOND); + + // MILLISECONDS + return String.format("%dms", iTime); + } + + public static Date getTodayWithTime(final String iTime) throws ParseException { + final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); + Calendar calParsed = Calendar.getInstance(); + calParsed.setTime(df.parse(iTime)); + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, calParsed.get(Calendar.HOUR_OF_DAY)); + cal.set(Calendar.MINUTE, calParsed.get(Calendar.MINUTE)); + cal.set(Calendar.SECOND, calParsed.get(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + public static String readFileAsString(final File iFile) throws IOException { + return readStreamAsString(new FileInputStream(iFile)); + } + + public static String readFileAsString(final File iFile, Charset iCharset) throws IOException { + return readStreamAsString(new FileInputStream(iFile), iCharset); + } + + public static String readStreamAsString(final InputStream iStream) throws IOException { + return readStreamAsString(iStream, StandardCharsets.UTF_8); + } + + public static String readStreamAsString(final InputStream iStream, Charset iCharset) throws IOException { + final StringBuffer fileData = new StringBuffer(1000); + final BufferedReader reader = new BufferedReader(new InputStreamReader(iStream, iCharset)); + try { + final char[] buf = new char[1024]; + int numRead = 0; + + while ((numRead = reader.read(buf)) != -1) { + String readData = String.valueOf(buf, 0, numRead); + + if (fileData.length() == 0 && readData.startsWith(UTF8_BOM)) + // SKIP UTF-8 BOM IF ANY + readData = readData.substring(1); + + fileData.append(readData); + } + } finally { + reader.close(); + } + return fileData.toString(); + + } + + public static void writeFile(final File iFile, final String iContent) throws IOException { + final FileOutputStream fos = new FileOutputStream(iFile); + try { + final OutputStreamWriter os = new OutputStreamWriter(fos); + try { + final BufferedWriter writer = new BufferedWriter(os); + try { + writer.write(iContent); + } finally { + writer.close(); + } + } finally { + os.close(); + } + } finally { + fos.close(); + } + } + + public static long copyStream(final InputStream in, final OutputStream out, long iMax) throws IOException { + if (iMax < 0) + iMax = Long.MAX_VALUE; + + final byte[] buf = new byte[8192]; + int byteRead = 0; + long byteTotal = 0; + while ((byteRead = in.read(buf, 0, (int) Math.min(buf.length, iMax - byteTotal))) > 0) { + out.write(buf, 0, byteRead); + byteTotal += byteRead; + } + return byteTotal; + } + + /** + * Returns the Unix file name format converting backslashes (\) to slasles (/) + */ + public static String getUnixFileName(final String iFileName) { + return iFileName != null ? iFileName.replace('\\', '/') : null; + } + + public static String getRelativePathIfAny(final String iDatabaseURL, final String iBasePath) { + if (iBasePath == null) { + final int pos = iDatabaseURL.lastIndexOf('/'); + if (pos > -1) + return iDatabaseURL.substring(pos + 1); + } else { + final int pos = iDatabaseURL.indexOf(iBasePath); + if (pos > -1) + return iDatabaseURL.substring(pos + iBasePath.length() + 1); + } + + return iDatabaseURL; + } + + public static String getDatabaseNameFromPath(final String iPath) { + return iPath.replace('/', '$'); + } + + public static String getPathFromDatabaseName(final String iPath) { + return iPath.replace('$', '/'); + } + + public static String getStringMaxLength(final String iText, final int iMax) { + return getStringMaxLength(iText, iMax, ""); + } + + public static String getStringMaxLength(final String iText, final int iMax, final String iOther) { + if (iText == null) + return null; + if (iMax > iText.length()) + return iText; + return iText.substring(0, iMax) + iOther; + } + + public static Object encode(final Object iValue) { + if (iValue instanceof String) { + return java2unicode(((String) iValue).replace("\\", "\\\\").replace("\"", "\\\"")); + } else + return iValue; + } + + public static String java2unicode(final String iInput) { + final StringBuilder result = new StringBuilder(iInput.length() * 2); + final int inputSize = iInput.length(); + + char ch; + String hex; + for (int i = 0; i < inputSize; i++) { + ch = iInput.charAt(i); + + if (ch >= 0x0020 && ch <= 0x007e) // Does the char need to be converted to unicode? + result.append(ch); // No. + else // Yes. + { + result.append("\\u"); // standard unicode format. + hex = Integer.toHexString(ch & 0xFFFF); // Get hex value of the char. + for (int j = 0; j < 4 - hex.length(); j++) + // Prepend zeros because unicode requires 4 digits + result.append('0'); + result.append(hex.toLowerCase(Locale.ENGLISH)); // standard unicode format. + // ostr.append(hex.toLowerCase(Locale.ENGLISH)); + } + } + + return result.toString(); + } + + public static boolean isStringContent(final Object iValue) { + if (iValue == null) + return false; + + final String s = iValue.toString(); + + if (s == null) + return false; + + return s.length() > 1 && (s.charAt(0) == '\'' && s.charAt(s.length() - 1) == '\'' + || s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"'); + } + + public static String getStringContent(final Object iValue) { + if (iValue == null) + return null; + + final String s = iValue.toString(); + + if (s == null) + return null; + + if (s.length() > 1 && (s.charAt(0) == '\'' && s.charAt(s.length() - 1) == '\'' + || s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"')) + return s.substring(1, s.length() - 1); + + if (s.length() > 1 && (s.charAt(0) == '`' && s.charAt(s.length() - 1) == '`')) + return s.substring(1, s.length() - 1); + + return s; + } + + public static String wrapStringContent(final Object iValue, final char iStringDelimiter) { + if (iValue == null) + return null; + + final String s = iValue.toString(); + + if (s == null) + return null; + + return iStringDelimiter + s + iStringDelimiter; + } + + public static boolean equals(final byte[] buffer, final byte[] buffer2) { + return Arrays.equals(buffer, buffer2); + } + + public static boolean isLong(final String iText) { + boolean isLong = true; + final int size = iText.length(); + for (int i = 0; i < size && isLong; i++) { + final char c = iText.charAt(i); + isLong = isLong & ((c >= '0' && c <= '9')); + } + return isLong; + } + + public static void readFully(InputStream in, byte[] b, int off, int len) throws IOException { + while (len > 0) { + int n = in.read(b, off, len); + + if (n == -1) { + throw new EOFException(); + } + off += n; + len -= n; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/io/OUtils.java b/core/src/main/java/com/orientechnologies/common/io/OUtils.java new file mode 100644 index 00000000000..653c8e3137a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/io/OUtils.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.io; + +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +public class OUtils { + public static String getDatabaseNameFromURL(final String name) { + if (OStringSerializerHelper.contains(name, '/')) + return name.substring(name.lastIndexOf("/") + 1); + return name; + } + + public static boolean equals(final Object a, final Object b) { + if (a == b) + return true; + + if (a != null) + return a.equals(b); + return b.equals(a); + } + + public static String camelCase(final String iText) { + return Character.toUpperCase(iText.charAt(0)) + iText.substring(1); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/listener/OListenerManger.java b/core/src/main/java/com/orientechnologies/common/listener/OListenerManger.java new file mode 100644 index 00000000000..850c220bbc5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/listener/OListenerManger.java @@ -0,0 +1,70 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.listener; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Abstract class to manage listeners. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * @param + * Listener type + */ +public abstract class OListenerManger { + private final Collection listeners; + + public OListenerManger(boolean concurrent) { + if (concurrent) + listeners = Collections.newSetFromMap(new ConcurrentHashMap()); + else + listeners = new HashSet(); + } + + public void registerListener(final L iListener) { + if (iListener != null) { + listeners.add(iListener); + } + } + + public void unregisterListener(final L iListener) { + if (iListener != null) { + listeners.remove(iListener); + } + } + + public void resetListeners() { + listeners.clear(); + } + + public Iterable browseListeners() { + return listeners; + } + + @SuppressWarnings("unchecked") + public Iterable getListenersCopy() { + return (Iterable) new HashSet(listeners); + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/listener/OProgressListener.java b/core/src/main/java/com/orientechnologies/common/listener/OProgressListener.java new file mode 100755 index 00000000000..4b31e4a9888 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/listener/OProgressListener.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.listener; + +import com.orientechnologies.orient.core.index.OIndex; + +/** + * Listener interface called on task execution. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OProgressListener { + public void onBegin(Object iTask, long iTotal, Object iMetadata); + + public boolean onProgress(Object iTask, long iCounter, float iPercent); + + public void onCompletition(Object iTask, boolean iSucceed); +} diff --git a/core/src/main/java/com/orientechnologies/common/log/OAnsiCode.java b/core/src/main/java/com/orientechnologies/common/log/OAnsiCode.java new file mode 100644 index 00000000000..a9ff58334f4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/log/OAnsiCode.java @@ -0,0 +1,118 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.log; + +import com.orientechnologies.common.parser.OVariableParser; +import com.orientechnologies.common.parser.OVariableParserListener; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +import java.util.Locale; + +/** + * Console ANSI utility class that supports most of the ANSI amenities. + * + * @author Luca Garulli + */ +public enum OAnsiCode { + + RESET("\u001B[0m"), + + // COLORS + BLACK("\u001B[30m"), RED("\u001B[31m"), GREEN("\u001B[32m"), YELLOW("\u001B[33m"), BLUE("\u001B[34m"), MAGENTA( + "\u001B[35m"), CYAN("\u001B[36m"), WHITE("\u001B[37m"), + + HIGH_INTENSITY("\u001B[1m"), LOW_INTENSITY("\u001B[2m"), + + ITALIC("\u001B[3m"), UNDERLINE("\u001B[4m"), BLINK("\u001B[5m"), RAPID_BLINK("\u001B[6m"), REVERSE_VIDEO( + "\u001B[7m"), INVISIBLE_TEXT("\u001B[8m"), + + BACKGROUND_BLACK("\u001B[40m"), BACKGROUND_RED("\u001B[41m"), BACKGROUND_GREEN("\u001B[42m"), BACKGROUND_YELLOW( + "\u001B[43m"), BACKGROUND_BLUE("\u001B[44m"), BACKGROUND_MAGENTA("\u001B[45m"), BACKGROUND_CYAN( + "\u001B[46m"), BACKGROUND_WHITE("\u001B[47m"), + + NULL(""); + + private String code; + + OAnsiCode(final String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } + + private final static boolean supportsColors; + + public static boolean isSupportsColors() { + return supportsColors; + } + + static { + final String ansiSupport = OGlobalConfiguration.LOG_SUPPORTS_ANSI.getValueAsString(); + if ("true".equalsIgnoreCase(ansiSupport)) + // FORCE ANSI SUPPORT + supportsColors = true; + else if ("auto".equalsIgnoreCase(ansiSupport)) { + // AUTOMATIC CHECK + if (System.console() != null && !System.getProperty("os.name").contains("Windows")) + supportsColors = true; + else + supportsColors = false; + } else + // DO NOT SUPPORT ANSI + supportsColors = false; + } + + public static String format(final String message) { + return format(message, supportsColors); + } + + public static String format(final String message, final boolean supportsColors) { + return (String) OVariableParser.resolveVariables(message, "$ANSI{", "}", new OVariableParserListener() { + @Override + public Object resolve(final String iVariable) { + final int pos = iVariable.indexOf(' '); + + final String text = pos > -1 ? iVariable.substring(pos + 1) : ""; + + if (supportsColors) { + final String code = pos > -1 ? iVariable.substring(0, pos) : iVariable; + + final StringBuilder buffer = new StringBuilder(); + + final String[] codes = code.split(":"); + for (int i = 0; i < codes.length; ++i) + buffer.append(OAnsiCode.valueOf(codes[i].toUpperCase(Locale.ENGLISH))); + + if (pos > -1) { + buffer.append(text); + buffer.append(OAnsiCode.RESET); + } + + return buffer.toString(); + } + + return text; + } + }); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/common/log/OAnsiLogFormatter.java b/core/src/main/java/com/orientechnologies/common/log/OAnsiLogFormatter.java new file mode 100644 index 00000000000..cba1e19b964 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/log/OAnsiLogFormatter.java @@ -0,0 +1,87 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.log; + +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static java.util.logging.Level.SEVERE; + +/** + * Log formatter that uses ANSI code if they are available and enabled. + * + * @author Luca Garulli + */ +public class OAnsiLogFormatter extends OLogFormatter { + + @Override + protected String customFormatMessage(final LogRecord iRecord) { + final Level level = iRecord.getLevel(); + final String message = OAnsiCode.format(iRecord.getMessage()); + final Object[] additionalArgs = iRecord.getParameters(); + final String requester = getSourceClassSimpleName(iRecord.getLoggerName()); + + final StringBuilder buffer = new StringBuilder(512); + buffer.append(EOL); + buffer.append("$ANSI{cyan "); + synchronized (dateFormat) { + buffer.append(dateFormat.format(new Date())); + } + buffer.append("}"); + + if (OAnsiCode.isSupportsColors()) { + if (level == SEVERE) + buffer.append("$ANSI{red "); + else if (level == Level.WARNING) + buffer.append("$ANSI{yellow "); + else if (level == Level.INFO) + buffer.append("$ANSI{green "); + else if (level == Level.CONFIG) + buffer.append("$ANSI{green "); + else if (level == Level.CONFIG) + buffer.append("$ANSI{white "); + } + + buffer.append(String.format(" %-5.5s ", level.getName())); + + if (OAnsiCode.isSupportsColors()) + buffer.append("}"); + + // FORMAT THE MESSAGE + try { + if (additionalArgs != null) + buffer.append(String.format(message, additionalArgs)); + else + buffer.append(message); + } catch (Exception e) { + buffer.append(message); + } + + if (requester != null) { + buffer.append(" ["); + buffer.append(requester); + buffer.append(']'); + } + + return OAnsiCode.format(buffer.toString()); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/log/OLogFormatter.java b/core/src/main/java/com/orientechnologies/common/log/OLogFormatter.java new file mode 100644 index 00000000000..53ea602821d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/log/OLogFormatter.java @@ -0,0 +1,112 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * Basic Log formatter. + * + * @author Luca Garulli + */ + +public class OLogFormatter extends Formatter { + + protected static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); + + /** + * The end-of-line character for this platform. + */ + protected static final String EOL = System.getProperty("line.separator"); + + @Override + public String format(final LogRecord record) { + if (record.getThrown() == null) { + return customFormatMessage(record); + } + + // FORMAT THE STACK TRACE + final StringBuilder buffer = new StringBuilder(512); + buffer.append(record.getMessage()); + + final Throwable current = record.getThrown(); + if (current != null) { + buffer.append(EOL); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + + current.printStackTrace(printWriter); + printWriter.flush(); + + buffer.append(writer.getBuffer()); + printWriter.close(); + } + + return buffer.toString(); + } + + protected String customFormatMessage(final LogRecord iRecord) { + final Level level = iRecord.getLevel(); + final String message = OAnsiCode.format(iRecord.getMessage(), false); + final Object[] additionalArgs = iRecord.getParameters(); + final String requester = getSourceClassSimpleName(iRecord.getLoggerName()); + + final StringBuilder buffer = new StringBuilder(512); + buffer.append(EOL); + synchronized (dateFormat) { + buffer.append(dateFormat.format(new Date())); + } + + buffer.append(String.format(" %-5.5s ", level.getName())); + + // FORMAT THE MESSAGE + try { + if (additionalArgs != null) + buffer.append(String.format(message, additionalArgs)); + else + buffer.append(message); + } catch (Exception e) { + buffer.append(message); + } + + if (requester != null) { + buffer.append(" ["); + buffer.append(requester); + buffer.append(']'); + } + + return OAnsiCode.format(buffer.toString(), false); + } + + protected String getSourceClassSimpleName(final String iSourceClassName) { + if(iSourceClassName==null) + return null; + return iSourceClassName.substring(iSourceClassName.lastIndexOf(".") + 1); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/log/OLogManager.java b/core/src/main/java/com/orientechnologies/common/log/OLogManager.java new file mode 100755 index 00000000000..7ce3560672e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/log/OLogManager.java @@ -0,0 +1,332 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.log; + +import com.orientechnologies.common.parser.OSystemVariableResolver; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.*; + +/** + * Centralized Log Manager. + * + * @author Luca Garulli + */ +public class OLogManager { + private static final String DEFAULT_LOG = "com.orientechnologies"; + private static final String ENV_INSTALL_CUSTOM_FORMATTER = "orientdb.installCustomFormatter"; + private static final OLogManager instance = new OLogManager(); + private boolean debug = false; + private boolean info = true; + private boolean warn = true; + private boolean error = true; + private Level minimumLevel = Level.SEVERE; + + private final ConcurrentMap loggersCache = new ConcurrentHashMap(); + + protected OLogManager() { + } + + public static OLogManager instance() { + return instance; + } + + public void installCustomFormatter() { + final boolean installCustomFormatter = Boolean + .parseBoolean(OSystemVariableResolver.resolveSystemVariables("${" + ENV_INSTALL_CUSTOM_FORMATTER + "}", "true")); + + if (!installCustomFormatter) + return; + + try { + // ASSURE TO HAVE THE ORIENT LOG FORMATTER TO THE CONSOLE EVEN IF NO CONFIGURATION FILE IS TAKEN + final Logger log = Logger.getLogger(""); + + setLevelInternal(log.getLevel()); + + if (log.getHandlers().length == 0) { + // SET DEFAULT LOG FORMATTER + final Handler h = new ConsoleHandler(); + h.setFormatter(new OAnsiLogFormatter()); + log.addHandler(h); + } else { + for (Handler h : log.getHandlers()) { + if (h instanceof ConsoleHandler && !h.getFormatter().getClass().equals(OAnsiLogFormatter.class)) + h.setFormatter(new OAnsiLogFormatter()); + } + } + } catch (Exception e) { + System.err.println("Error while installing custom formatter. Logging could be disabled. Cause: " + e.toString()); + } + } + + public void setConsoleLevel(final String iLevel) { + setLevel(iLevel, ConsoleHandler.class); + } + + public void setFileLevel(final String iLevel) { + setLevel(iLevel, FileHandler.class); + } + + public void log(final Object iRequester, final Level iLevel, String iMessage, final Throwable iException, + final Object... iAdditionalArgs) { + if (iMessage != null) { + try { + final ODatabaseDocumentInternal db = + ODatabaseRecordThreadLocal.INSTANCE != null ? ODatabaseRecordThreadLocal.INSTANCE.getIfDefined() : null; + if (db != null && db.getStorage() != null && db.getStorage() instanceof OAbstractPaginatedStorage) { + final String dbName = db.getStorage().getName(); + if (dbName != null) + iMessage = "$ANSI{green {db=" + dbName + "}} " + iMessage; + } + } catch (Throwable e) { + } + + final String requesterName; + if (iRequester instanceof Class) { + requesterName = ((Class) iRequester).getName(); + } else if (iRequester != null) { + requesterName = iRequester.getClass().getName(); + } else { + requesterName = DEFAULT_LOG; + } + + Logger log = loggersCache.get(requesterName); + if (log == null) { + log = Logger.getLogger(requesterName); + + if (log != null) { + Logger oldLogger = loggersCache.putIfAbsent(requesterName, log); + + if (oldLogger != null) + log = oldLogger; + } + } + + if (log == null) { + // USE SYSERR + try { + System.err.println(String.format(iMessage, iAdditionalArgs)); + } catch (Exception e) { + OLogManager.instance().warn(this, "Error on formatting message", e); + } + } else if (log.isLoggable(iLevel)) { + // USE THE LOG + try { + final String msg = String.format(iMessage, iAdditionalArgs); + if (iException != null) + log.log(iLevel, msg, iException); + else + log.log(iLevel, msg); + } catch (Exception e) { + System.err.print(String.format("Error on formatting message '%s'. Exception: %s", iMessage, e.toString())); + } + } + } + } + + public void debug(final Object iRequester, final String iMessage, final Object... iAdditionalArgs) { + if (isDebugEnabled()) + log(iRequester, Level.FINE, iMessage, null, iAdditionalArgs); + } + + public void debug(final Object iRequester, final String iMessage, final Throwable iException, final Object... iAdditionalArgs) { + if (isDebugEnabled()) + log(iRequester, Level.FINE, iMessage, iException, iAdditionalArgs); + } + + public void info(final Object iRequester, final String iMessage, final Object... iAdditionalArgs) { + if (isInfoEnabled()) + log(iRequester, Level.INFO, iMessage, null, iAdditionalArgs); + } + + public void info(final Object iRequester, final String iMessage, final Throwable iException, final Object... iAdditionalArgs) { + if (isInfoEnabled()) + log(iRequester, Level.INFO, iMessage, iException, iAdditionalArgs); + } + + public void warn(final Object iRequester, final String iMessage, final Object... iAdditionalArgs) { + if (isWarnEnabled()) + log(iRequester, Level.WARNING, iMessage, null, iAdditionalArgs); + } + + public void warn(final Object iRequester, final String iMessage, final Throwable iException, final Object... iAdditionalArgs) { + if (isWarnEnabled()) + log(iRequester, Level.WARNING, iMessage, iException, iAdditionalArgs); + } + + public void config(final Object iRequester, final String iMessage, final Object... iAdditionalArgs) { + log(iRequester, Level.CONFIG, iMessage, null, iAdditionalArgs); + } + + public void error(final Object iRequester, final String iMessage, final Object... iAdditionalArgs) { + log(iRequester, Level.SEVERE, iMessage, null, iAdditionalArgs); + } + + public void error(final Object iRequester, final String iMessage, final Throwable iException, final Object... iAdditionalArgs) { + if (isErrorEnabled()) + log(iRequester, Level.SEVERE, iMessage, iException, iAdditionalArgs); + } + + public boolean isWarn() { + return warn; + } + + public boolean isLevelEnabled(final Level level) { + if (level.equals(Level.FINER) || level.equals(Level.FINE) || level.equals(Level.FINEST)) + return debug; + else if (level.equals(Level.INFO)) + return info; + else if (level.equals(Level.WARNING)) + return warn; + else if (level.equals(Level.SEVERE)) + return error; + return false; + } + + public boolean isDebugEnabled() { + return debug; + } + + public void setDebugEnabled(boolean debug) { + this.debug = debug; + } + + public boolean isInfoEnabled() { + return info; + } + + public void setInfoEnabled(boolean info) { + this.info = info; + } + + public boolean isWarnEnabled() { + return warn; + } + + public void setWarnEnabled(boolean warn) { + this.warn = warn; + } + + public boolean isErrorEnabled() { + return error; + } + + public void setErrorEnabled(boolean error) { + this.error = error; + } + + public Level setLevel(final String iLevel, final Class iHandler) { + final Level level = iLevel != null ? Level.parse(iLevel.toUpperCase(Locale.ENGLISH)) : Level.INFO; + + if (level.intValue() < minimumLevel.intValue()) { + // UPDATE MINIMUM LEVEL + minimumLevel = level; + + setLevelInternal(level); + } + + Logger log = Logger.getLogger(DEFAULT_LOG); + while (log != null) { + + for (Handler h : log.getHandlers()) { + if (h.getClass().isAssignableFrom(iHandler)) { + h.setLevel(level); + break; + } + } + + log = log.getParent(); + } + + return level; + } + + protected void setLevelInternal(final Level level) { + if (level == null) + return; + + if (level.equals(Level.FINER) || level.equals(Level.FINE) || level.equals(Level.FINEST)) + debug = info = warn = error = true; + else if (level.equals(Level.INFO)) { + info = warn = error = true; + debug = false; + } else if (level.equals(Level.WARNING)) { + warn = error = true; + debug = info = false; + } else if (level.equals(Level.SEVERE)) { + error = true; + debug = info = warn = false; + } + } + + public void flush() { + for (Handler h : Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).getHandlers()) + h.flush(); + } + + public OCommandOutputListener getCommandOutputListener(final Object iThis, final Level iLevel) { + return new OCommandOutputListener() { + @Override + public void onMessage(String iText) { + log(iThis, iLevel, iText, null); + } + }; + } + + /** + * Shutdowns this log manager. + */ + public void shutdown() { + try { + if (LogManager.getLogManager() instanceof DebugLogManager) + ((DebugLogManager) LogManager.getLogManager()).shutdown(); + } catch (NoClassDefFoundError e) { + // Om nom nom. Some custom class loaders, like Tomcat's one, cannot load classes while in shutdown hooks, since their + // runtime is already shutdown. Ignoring the exception, if DebugLogManager is not loaded at this point there are no instances + // of it anyway and we have nothing to shutdown. + } + } + + /** + * Inhibits the logs reset request which is typically done on shutdown. This allows to use JDK logging from shutdown hooks. + * -Djava.util.logging.manager=com.orientechnologies.common.log.OLogManager$DebugLogManager must be passed to the JVM, + * to activate this log manager. + */ + public static class DebugLogManager extends LogManager { + + @Override + public void reset() { + // do nothing + } + + private void shutdown() { + super.reset(); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OBaseParser.java b/core/src/main/java/com/orientechnologies/common/parser/OBaseParser.java new file mode 100644 index 00000000000..8f4af68814b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OBaseParser.java @@ -0,0 +1,644 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.parser; + +import java.util.Arrays; + +/** + * Abstract generic command to parse. + * + * @author Luca Garulli + */ +public abstract class OBaseParser { + public String parserText; + public String parserTextUpperCase; + + private transient StringBuilder parserLastWord = new StringBuilder(256); + private transient int parserEscapeSequenceCount = 0; + private transient int parserCurrentPos = 0; + private transient int parserPreviousPos = 0; + private transient char parserLastSeparator = ' '; + + public static int nextWord(final String iText, final String iTextUpperCase, int ioCurrentPosition, final StringBuilder ioWord, + final boolean iForceUpperCase) { + return nextWord(iText, iTextUpperCase, ioCurrentPosition, ioWord, iForceUpperCase, " =><(),"); + } + + public static int nextWord(final String iText, final String iTextUpperCase, int ioCurrentPosition, final StringBuilder ioWord, + final boolean iForceUpperCase, final String iSeparatorChars) { + ioWord.setLength(0); + + ioCurrentPosition = OStringParser.jumpWhiteSpaces(iText, ioCurrentPosition, -1); + if (ioCurrentPosition < 0) + return -1; + + getWordStatic(iForceUpperCase ? iTextUpperCase : iText, ioCurrentPosition, iSeparatorChars, ioWord); + + if (ioWord.length() > 0) + ioCurrentPosition += ioWord.length(); + + return ioCurrentPosition; + } + + /** + * @param iText Text where to search + * @param iBeginIndex Begin index + * @param iSeparatorChars Separators as a String of multiple characters + * @param ioBuffer StringBuilder object with the word found + */ + public static void getWordStatic(final CharSequence iText, int iBeginIndex, final String iSeparatorChars, + final StringBuilder ioBuffer) { + ioBuffer.setLength(0); + + char stringBeginChar = ' '; + char c; + + for (int i = iBeginIndex; i < iText.length(); ++i) { + c = iText.charAt(i); + boolean found = false; + for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) { + if (iSeparatorChars.charAt(sepIndex) == c) { + // SEPARATOR AT THE BEGINNING: JUMP IT + found = true; + break; + } + } + if (!found) + break; + + iBeginIndex++; + } + + for (int i = iBeginIndex; i < iText.length(); ++i) { + c = iText.charAt(i); + + if (c == '\'' || c == '"' || c == '`') { + if (stringBeginChar != ' ') { + // CLOSE THE STRING? + if (stringBeginChar == c) { + // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH + stringBeginChar = ' '; + } + } else { + // START STRING + stringBeginChar = c; + } + } else if (stringBeginChar == ' ') { + for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) { + if (iSeparatorChars.charAt(sepIndex) == c && ioBuffer.length() > 0) { + // SEPARATOR (OUTSIDE A STRING): PUSH + return; + } + } + } + + ioBuffer.append(c); + } + } + + public String getSyntax() { + return "?"; + } + + /** + * Returns the last separator encountered, otherwise returns a blank (' '). + */ + public char parserGetLastSeparator() { + return parserLastSeparator; + } + + /** + * Overwrites the last separator. To ignore it set it to blank (' '). + */ + public void parserSetLastSeparator(final char iSeparator) { + parserLastSeparator = iSeparator; + } + + /** + * Returns the cursor position before last parsing. + * + * @return Offset from the beginning + */ + public int parserGetPreviousPosition() { + return parserPreviousPos; + } + + /** + * Tells if the parsing has reached the end of the content. + * + * @return True if is ended, otherwise false + */ + public boolean parserIsEnded() { + return parserCurrentPos == -1; + } + + /** + * Returns the current cursor position. + * + * @return Offset from the beginning + */ + public int parserGetCurrentPosition() { + return parserCurrentPos; + } + + /** + * Returns the current character in the current cursor position + * + * @return The current character in the current cursor position. If the end is reached, then a blank (' ') is returned + */ + public char parserGetCurrentChar() { + if (parserCurrentPos < 0) + return ' '; + return parserText.charAt(parserCurrentPos); + } + + /** + * Returns the last parsed word. + * + * @return Last parsed word as String + */ + public String parserGetLastWord() { + return parserLastWord.toString(); + } + + public int getLastWordLength() { + return parserLastWord.length() + parserEscapeSequenceCount; + } + + /** + * Throws a syntax error exception. + * + * @param iText Text about the problem. + */ + protected abstract void throwSyntaxErrorException(final String iText); + + /** + * Parses the next word. It returns the word parsed if any. + * + * @param iUpperCase True if must return UPPERCASE, otherwise false + * @return The word parsed if any, otherwise null + */ + protected String parserOptionalWord(final boolean iUpperCase) { + parserPreviousPos = parserCurrentPos; + + parserNextWord(iUpperCase); + if (parserLastWord.length() == 0) + return null; + return parserLastWord.toString(); + } + + /** + * Parses the next word. If any word is parsed it's checked against the word array received as parameter. If the parsed word is + * not enlisted in it a SyntaxError exception is thrown. It returns the word parsed if any. + * + * @param iUpperCase True if must return UPPERCASE, otherwise false + * @return The word parsed if any, otherwise null + */ + protected String parseOptionalWord(final boolean iUpperCase, final String... iWords) { + parserNextWord(iUpperCase); + + if (iWords.length > 0) { + if (parserLastWord.length() == 0) + return null; + + boolean found = false; + for (String w : iWords) { + if (parserLastWord.toString().equals(w)) { + found = true; + break; + } + } + + if (!found) + throwSyntaxErrorException( + "Found unexpected keyword '" + parserLastWord + "' while it was expected '" + Arrays.toString(iWords) + "'"); + } + + if (parserLastWord.length() > 1 && parserLastWord.charAt(0) == '`' + && parserLastWord.charAt(parserLastWord.length() - 1) == '`') { + return parserLastWord.substring(1, parserLastWord.length() - 1); + } + + return parserLastWord.toString(); + } + + /** + * Goes back to the previous position. + * + * @return The previous position + */ + protected int parserGoBack() { + parserCurrentPos = parserPreviousPos; + return parserCurrentPos; + } + + /** + * Parses the next word. If no word is found an SyntaxError exception is thrown It returns the word parsed if any. + * + * @param iUpperCase True if must return UPPERCASE, otherwise false + * @return The word parsed + */ + protected String parserRequiredWord(final boolean iUpperCase) { + return parserRequiredWord(iUpperCase, "Syntax error", null); + } + + /** + * Parses the next word. If no word is found an SyntaxError exception with the custom message received as parameter is thrown It + * returns the word parsed if any. + * + * @param iUpperCase True if must return UPPERCASE, otherwise false + * @param iCustomMessage Custom message to include in case of SyntaxError exception + * @return The word parsed + */ + protected String parserRequiredWord(final boolean iUpperCase, final String iCustomMessage) { + return parserRequiredWord(iUpperCase, iCustomMessage, null); + } + + /** + * Parses the next word. If no word is found or the parsed word is not present in the word array received as parameter then a + * SyntaxError exception with the custom message received as parameter is thrown. It returns the word parsed if any. + * + * @param iUpperCase True if must return UPPERCASE, otherwise false + * @param iCustomMessage Custom message to include in case of SyntaxError exception + * @param iSeparators Separator characters + * @return The word parsed + */ + + protected String parserRequiredWord(final boolean iUpperCase, final String iCustomMessage, String iSeparators) { + return parserRequiredWord(iUpperCase, iCustomMessage, iSeparators, false); + } + + protected String parserRequiredWord(final boolean iUpperCase, final String iCustomMessage, String iSeparators, + boolean preserveQuotes) { + if (iSeparators == null) + iSeparators = " ()=><,\r\n"; + + parserNextWord(iUpperCase, iSeparators, preserveQuotes); + if (parserLastWord.length() == 0) + throwSyntaxErrorException(iCustomMessage); + if (parserLastWord.charAt(0) == '`' && parserLastWord.charAt(parserLastWord.length() - 1) == '`') { + return parserLastWord.substring(1, parserLastWord.length() - 1); + } + return parserLastWord.toString(); + } + + /** + * Parses the next word. If no word is found or the parsed word is not present in the word array received as parameter then a + * SyntaxError exception is thrown. + * + * @param iWords Array of expected keywords + */ + protected void parserRequiredKeyword(final String... iWords) { + parserNextWord(true, " \r\n,()"); + if (parserLastWord.length() == 0) + throwSyntaxErrorException("Cannot find expected keyword '" + Arrays.toString(iWords) + "'"); + + boolean found = false; + for (String w : iWords) { + if (parserLastWord.toString().equals(w)) { + found = true; + break; + } + } + + if (!found) + throwSyntaxErrorException( + "Found unexpected keyword '" + parserLastWord + "' while it was expected '" + Arrays.toString(iWords) + "'"); + } + + /** + * Parses the next sequence of chars. + * + * @return The position of the word matched if any, otherwise -1 or an exception if iMandatory is true + */ + protected int parserNextChars(final boolean iUpperCase, final boolean iMandatory, final String... iCandidateWords) { + parserPreviousPos = parserCurrentPos; + parserSkipWhiteSpaces(); + + parserEscapeSequenceCount = 0; + parserLastWord.setLength(0); + + final String[] processedWords = Arrays.copyOf(iCandidateWords, iCandidateWords.length); + + // PARSE THE CHARS + final String text2Use = iUpperCase ? parserTextUpperCase : parserText; + final int max = text2Use.length(); + + parserCurrentPos = parserCurrentPos + parserTextUpperCase.length() - parserText.length(); + // PARSE TILL 1 CHAR AFTER THE END TO SIMULATE A SEPARATOR AS EOF + for (int i = 0; parserCurrentPos <= max; ++i) { + final char ch = parserCurrentPos < max ? text2Use.charAt(parserCurrentPos) : '\n'; + final boolean separator = ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t' || ch == '('; + if (!separator) + parserLastWord.append(ch); + + // CLEAR CANDIDATES + int candidatesWordsCount = 0; + int candidatesWordsPos = -1; + for (int c = 0; c < processedWords.length; ++c) { + final String w = processedWords[c]; + if (w != null) { + final int wordSize = w.length(); + if ((separator && wordSize > i) || (!separator && (i > wordSize - 1 || w.charAt(i) != ch))) + // DISCARD IT + processedWords[c] = null; + else { + candidatesWordsCount++; + if (candidatesWordsCount == 1) + // REMEMBER THE POSITION + candidatesWordsPos = c; + } + } + } + + if (candidatesWordsCount == 1) { + // ONE RESULT, CHECKING IF FOUND + final String w = processedWords[candidatesWordsPos]; + if (w.length() == i + (separator ? 0 : 1) && !Character.isLetter(ch)) + // FOUND! + return candidatesWordsPos; + } + + if (candidatesWordsCount == 0 || separator) + break; + + parserCurrentPos++; + } + + if (iMandatory) + throwSyntaxErrorException( + "Found unexpected keyword '" + parserLastWord + "' while it was expected '" + Arrays.toString(iCandidateWords) + "'"); + + return -1; + } + + /** + * Parses optional keywords between the iWords. If a keyword is found but doesn't match with iWords then a SyntaxError is raised. + * + * @param iWords Optional words to match as keyword. If at least one is passed, then the check is made + * @return true if a keyword was found, otherwise false + */ + protected boolean parserOptionalKeyword(final String... iWords) { + parserNextWord(true, " \r\n,"); + if (parserLastWord.length() == 0) + return false; + + // FOUND: CHECK IF IT'S IN RANGE + boolean found = iWords.length == 0; + for (String w : iWords) { + if (parserLastWord.toString().equals(w)) { + found = true; + break; + } + } + + if (!found) + throwSyntaxErrorException( + "Found unexpected keyword '" + parserLastWord + "' while it was expected '" + Arrays.toString(iWords) + "'"); + + return true; + } + + /** + * Skips not valid characters like spaces and line feeds. + * + * @return True if the string is not ended, otherwise false + */ + protected boolean parserSkipWhiteSpaces() { + if (parserCurrentPos == -1) + return false; + + parserCurrentPos = OStringParser.jumpWhiteSpaces(parserText, parserCurrentPos, -1); + return parserCurrentPos > -1; + } + + /** + * Overwrites the current cursor position. + * + * @param iPosition New position + * @return True if the string is not ended, otherwise false + */ + protected boolean parserSetCurrentPosition(final int iPosition) { + parserCurrentPos = iPosition; + if (parserCurrentPos >= parserText.length()) + // END OF TEXT + parserCurrentPos = -1; + return parserCurrentPos > -1; + } + + /** + * Sets the end of text as position + */ + protected void parserSetEndOfText() { + parserCurrentPos = -1; + } + + /** + * Moves the current cursor position forward or backward of iOffset characters + * + * @param iOffset Number of characters to move. Negative numbers means backwards + * @return True if the string is not ended, otherwise false + */ + protected boolean parserMoveCurrentPosition(final int iOffset) { + if (parserCurrentPos < 0) + return false; + return parserSetCurrentPosition(parserCurrentPos + iOffset); + } + + /** + * Parses the next word. + * + * @param iForceUpperCase True if must return UPPERCASE, otherwise false + */ + protected String parserNextWord(final boolean iForceUpperCase) { + return parserNextWord(iForceUpperCase, " =><(),\r\n"); + } + + /** + * Parses the next word. + * + * @param iForceUpperCase True if must return UPPERCASE, otherwise false + * @param iSeparatorChars + */ + protected String parserNextWord(final boolean iForceUpperCase, final String iSeparatorChars) { + return parserNextWord(iForceUpperCase, iSeparatorChars, false); + } + + protected String parserNextWord(final boolean iForceUpperCase, final String iSeparatorChars, boolean preserveEscapes) { + parserPreviousPos = parserCurrentPos; + parserLastWord.setLength(0); + parserEscapeSequenceCount = 0; + + parserSkipWhiteSpaces(); + if (parserCurrentPos == -1) + return null; + + char stringBeginChar = ' '; + + final String text2Use = iForceUpperCase ? parserTextUpperCase : parserText; + + while (parserCurrentPos < text2Use.length()) { + final char c = text2Use.charAt(parserCurrentPos); + boolean found = false; + for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) { + if (iSeparatorChars.charAt(sepIndex) == c) { + // SEPARATOR AT THE BEGINNING: JUMP IT + found = true; + break; + } + } + if (!found) + break; + + parserCurrentPos++; + } + + try { + int openParenthesis = 0; + int openBracket = 0; + int openGraph = 0; + + int escapePos = -1; + + for (; parserCurrentPos < text2Use.length(); parserCurrentPos++) { + final char c = text2Use.charAt(parserCurrentPos); + + if (escapePos == -1 && c == '\\' && ((parserCurrentPos + 1) < text2Use.length())) { + // ESCAPE CHARS + + if (openGraph == 0) { + final char nextChar = text2Use.charAt(parserCurrentPos + 1); + if (preserveEscapes) { + parserLastWord.append(c); + parserLastWord.append(nextChar); + parserCurrentPos++; + } else { + + if (nextChar == 'u') { + parserCurrentPos = OStringParser.readUnicode(text2Use, parserCurrentPos + 2, parserLastWord); + parserEscapeSequenceCount += 5; + } else { + if (nextChar == 'n') + parserLastWord.append('\n'); + else if (nextChar == 'r') + parserLastWord.append('\r'); + else if (nextChar == 't') + parserLastWord.append('\t'); + else if (nextChar == 'b') + parserLastWord.append('\b'); + else if (nextChar == 'f') + parserLastWord.append('\f'); + else { + parserLastWord.append(nextChar); + parserEscapeSequenceCount++; + } + + parserCurrentPos++; + } + } + continue; + } else + escapePos = parserCurrentPos; + } + + if (escapePos == -1 && (c == '\'' || c == '"')) { + if (stringBeginChar != ' ') { + // CLOSE THE STRING? + if (stringBeginChar == c) { + // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH + stringBeginChar = ' '; + + if (openBracket == 0 && openGraph == 0 && openParenthesis == 0) { + parserCurrentPos++; + parserLastWord.append(c); + break; + } + } + } else + // START STRING + stringBeginChar = c; + } + + if (stringBeginChar == ' ') { + if (openBracket == 0 && openGraph == 0 && openParenthesis == 0 && parserCheckSeparator(c, iSeparatorChars)) { + // SEPARATOR FOUND! + break; + } else if (c == '(') + openParenthesis++; + else if (c == ')' && openParenthesis > 0) + openParenthesis--; + else if (c == '[') + openBracket++; + else if (c == ']' && openBracket > 0) + openBracket--; + else if (c == '{') + openGraph++; + else if (c == '}' && openGraph > 0) + openGraph--; + } + + if (escapePos != -1) + parserEscapeSequenceCount++; + + if (escapePos != parserCurrentPos) + escapePos = -1; + + parserLastWord.append(c); + } + + // CHECK MISSING CHARACTER + if (stringBeginChar != ' ') + throw new IllegalStateException( + "Missing closed string character: '" + stringBeginChar + "', position: " + parserCurrentPos); + if (openBracket > 0) + throw new IllegalStateException("Missing closed braket character: ']', position: " + parserCurrentPos); + if (openGraph > 0) + throw new IllegalStateException("Missing closed graph character: '}', position: " + parserCurrentPos); + if (openParenthesis > 0) + throw new IllegalStateException("Missing closed parenthesis character: ')', position: " + parserCurrentPos); + + } finally { + if (parserCurrentPos >= text2Use.length()) { + // END OF TEXT + parserCurrentPos = -1; + parserLastSeparator = ' '; + } + } + + return parserLastWord.toString(); + } + + /** + * Check for a separator + * + * @param c + * @param iSeparatorChars + * @return + */ + private boolean parserCheckSeparator(final char c, final String iSeparatorChars) { + for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) { + if (iSeparatorChars.charAt(sepIndex) == c) { + parserLastSeparator = c; + return true; + } + } + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OContextVariableResolver.java b/core/src/main/java/com/orientechnologies/common/parser/OContextVariableResolver.java new file mode 100644 index 00000000000..7fc084e33c6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OContextVariableResolver.java @@ -0,0 +1,63 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; + +/** + * Resolve variables by using a context. + * + * @author Luca Garulli (luca.garulli--at--assetdata.it) + * + */ +public class OContextVariableResolver implements OVariableParserListener { + public static final String VAR_BEGIN = "${"; + public static final String VAR_END = "}"; + + private final OCommandContext context; + + public OContextVariableResolver(final OCommandContext iContext) { + this.context = iContext; + } + + public String parse(final String iValue) { + return parse(iValue, null); + } + + public String parse(final String iValue, final String iDefault) { + if (iValue == null) + return iDefault; + + return (String) OVariableParser.resolveVariables(iValue, VAR_BEGIN, VAR_END, this, iDefault); + } + + @Override + public String resolve(final String variable) { + if (variable == null) + return null; + + final Object value = context.getVariable(variable); + + if (value != null) + return value.toString(); + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OStringParser.java b/core/src/main/java/com/orientechnologies/common/parser/OStringParser.java new file mode 100644 index 00000000000..e9981e01fd6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OStringParser.java @@ -0,0 +1,397 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.parser; + +import java.util.ArrayList; + +/** + * String parser utility class + * + * @author Luca Garulli + * + */ +public class OStringParser { + + public static final String WHITE_SPACE = " "; + public static final String COMMON_JUMP = " \r\n"; + + public static String[] getWords(String iRecord, final String iSeparatorChars) { + return getWords(iRecord, iSeparatorChars, false); + } + + public static String[] getWords(String iRecord, final String iSeparatorChars, final boolean iIncludeStringSep) { + return getWords(iRecord, iSeparatorChars, " \n\r\t", iIncludeStringSep); + } + + public static String[] getWords(String iText, final String iSeparatorChars, final String iJumpChars, + final boolean iIncludeStringSep) { + iText = iText.trim(); + + final ArrayList fields = new ArrayList(); + final StringBuilder buffer = new StringBuilder(64); + char stringBeginChar = ' '; + char c; + int openBraket = 0; + int openGraph = 0; + boolean charFound; + boolean escape = false; + + for (int i = 0; i < iText.length(); ++i) { + c = iText.charAt(i); + + if (!escape && c == '\\' && ((i + 1) < iText.length())) { + // ESCAPE CHARS + final char nextChar = iText.charAt(i + 1); + + if (nextChar == 'u') { + i = readUnicode(iText, i + 2, buffer); + } else if (nextChar == 'n') { + buffer.append(stringBeginChar == ' ' ? "\n" : "\\\n"); + i++; + } else if (nextChar == 'r') { + buffer.append(stringBeginChar == ' ' ? "\r" : "\\\r"); + i++; + } else if (nextChar == 't') { + buffer.append(stringBeginChar == ' ' ? "\t" : "\\\t"); + i++; + } else if (nextChar == 'f') { + buffer.append(stringBeginChar == ' ' ? "\f" : "\\\f"); + i++; + } else if (stringBeginChar != ' ' && nextChar == '\'' || nextChar == '"') { + buffer.append('\\'); + buffer.append(nextChar); + i++; + } else { + buffer.append('\\'); + escape = true; + } + + continue; + } + + if (!escape && (c == '\'' || c == '"')) { + if (stringBeginChar != ' ') { + // CLOSE THE STRING? + if (stringBeginChar == c) { + // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH + stringBeginChar = ' '; + + if (iIncludeStringSep) + buffer.append(c); + continue; + } + } else { + // START STRING + stringBeginChar = c; + if (iIncludeStringSep) + buffer.append(c); + + continue; + } + } else if (stringBeginChar == ' ') { + if (c == '[') + openBraket++; + else if (c == ']') + openBraket--; + else if (c == '{') + openGraph++; + else if (c == '}') + openGraph--; + else if (openBraket == 0 && openGraph == 0) { + charFound = false; + for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) { + if (iSeparatorChars.charAt(sepIndex) == c) { + charFound = true; + if (buffer.length() > 0) { + // SEPARATOR (OUTSIDE A STRING): PUSH + fields.add(buffer.toString()); + buffer.setLength(0); + } + break; + } + } + + if (charFound) + continue; + } + + if (stringBeginChar == ' ') { + // CHECK FOR CHAR TO JUMP + charFound = false; + + for (int jumpIndex = 0; jumpIndex < iJumpChars.length(); ++jumpIndex) { + if (iJumpChars.charAt(jumpIndex) == c) { + charFound = true; + break; + } + } + + if (charFound) + continue; + } + } + + buffer.append(c); + + if (escape) + escape = false; + } + + if (buffer.length() > 0) { + // ADD THE LAST WORD IF ANY + fields.add(buffer.toString()); + } + + String[] result = new String[fields.size()]; + fields.toArray(result); + return result; + } + + public static String[] split(String iText, final char iSplitChar, String iJumpChars) { + iText = iText.trim(); + + ArrayList fields = new ArrayList(); + StringBuilder buffer = new StringBuilder(256); + char c; + char stringChar = ' '; + boolean escape = false; + boolean jumpSplitChar = false; + boolean charFound; + + for (int i = 0; i < iText.length(); i++) { + c = iText.charAt(i); + + if (!escape && c == '\\' && ((i + 1) < iText.length())) { + if (iText.charAt(i + 1) == 'u') { + i = readUnicode(iText, i + 2, buffer); + } else { + escape = true; + buffer.append(c); + } + continue; + } + + if (c == '\'' || c == '"') { + if (!jumpSplitChar) { + jumpSplitChar = true; + stringChar = c; + } else { + if (!escape && c == stringChar) + jumpSplitChar = false; + } + } + + if (c == iSplitChar) { + if (!jumpSplitChar) { + fields.add(buffer.toString()); + buffer.setLength(0); + continue; + } + } + + // CHECK IF IT MUST JUMP THE CHAR + if (buffer.length() == 0) { + charFound = false; + + for (int jumpIndex = 0; jumpIndex < iJumpChars.length(); ++jumpIndex) { + if (iJumpChars.charAt(jumpIndex) == c) { + charFound = true; + break; + } + } + + if (charFound) + continue; + } + + buffer.append(c); + + if (escape) + escape = false; + } + + if (buffer.length() > 0) { + fields.add(buffer.toString()); + buffer.setLength(0); + } + String[] result = new String[fields.size()]; + fields.toArray(result); + return result; + } + + /** + * Finds a character inside a string specyfing the limits and direction. If iFrom is minor than iTo, then it moves forward, + * otherwise backward. + */ + public static int indexOfOutsideStrings(final String iText, final char iToFind, int iFrom, int iTo) { + if (iTo == -1) + iTo = iText.length() - 1; + if (iFrom == -1) + iFrom = iText.length() - 1; + + char c; + char stringChar = ' '; + boolean escape = false; + + final StringBuilder buffer = new StringBuilder(1024); + + int i = iFrom; + while (true) { + c = iText.charAt(i); + + if (!escape && c == '\\' && ((i + 1) < iText.length())) { + if (iText.charAt(i + 1) == 'u') { + i = readUnicode(iText, i + 2, buffer); + } else + escape = true; + } else { + if (c == '\'' || c == '"') { + // BEGIN/END STRING + if (stringChar == ' ') { + // BEGIN + stringChar = c; + } else { + // END + if (!escape && c == stringChar) + stringChar = ' '; + } + } + + if (c == iToFind && stringChar == ' ') + return i; + + if (escape) + escape = false; + } + + if (iFrom < iTo) { + // MOVE FORWARD + if (++i > iTo) + break; + } else { + // MOVE BACKWARD + if (--i < iFrom) + break; + } + } + return -1; + } + + /** + * Jump white spaces. + * + * @param iText + * String to analyze + * @param iCurrentPosition + * Current position in text + * @param iMaxPosition + * TODO + * @return The new offset inside the string analyzed + */ + public static int jumpWhiteSpaces(final CharSequence iText, final int iCurrentPosition, final int iMaxPosition) { + return jump(iText, iCurrentPosition, iMaxPosition, COMMON_JUMP); + } + + /** + * Jump some characters reading from an offset of a String. + * + * @param iText + * String to analyze + * @param iCurrentPosition + * Current position in text + * @param iMaxPosition + * Maximum position to read + * @param iJumpChars + * String as char array of chars to jump + * @return The new offset inside the string analyzed + */ + public static int jump(final CharSequence iText, int iCurrentPosition, final int iMaxPosition, final String iJumpChars) { + if (iCurrentPosition < 0) + return -1; + + final int size = iMaxPosition > -1 ? Math.min(iMaxPosition, iText.length()) : iText.length(); + final int jumpCharSize = iJumpChars.length(); + boolean found = true; + char c; + for (; iCurrentPosition < size; ++iCurrentPosition) { + found = false; + c = iText.charAt(iCurrentPosition); + for (int jumpIndex = 0; jumpIndex < jumpCharSize; ++jumpIndex) { + if (iJumpChars.charAt(jumpIndex) == c) { + found = true; + break; + } + } + + if (!found) + break; + } + + return iCurrentPosition >= size ? -1 : iCurrentPosition; + } + + public static int readUnicode(String iText, int position, final StringBuilder buffer) { + // DECODE UNICODE CHAR + final StringBuilder buff = new StringBuilder(64); + final int lastPos = position + 4; + for (; position < lastPos; ++position) + buff.append(iText.charAt(position)); + + buffer.append((char) Integer.parseInt(buff.toString(), 16)); + return position - 1; + } + + public static int readUnicode(char[] iText, int position, final StringBuilder buffer) { + // DECODE UNICODE CHAR + final StringBuilder buff = new StringBuilder(64); + final int lastPos = position + 4; + for (; position < lastPos; ++position) + buff.append(iText[position]); + + buffer.append((char) Integer.parseInt(buff.toString(), 16)); + return position - 1; + } + + public static String replaceAll(final String iText, final String iToReplace, final String iReplacement) { + if (iText == null || iText.length() <= 0 || iToReplace == null || iToReplace.length() <= 0) + return iText; + int pos = iText.indexOf(iToReplace); + int lastAppend = 0; + final StringBuffer buffer = new StringBuffer(1024); + while (pos > -1) { + buffer.append(iText.substring(lastAppend, pos)); + buffer.append(iReplacement); + lastAppend = pos + iToReplace.length(); + pos = iText.indexOf(iToReplace, lastAppend); + } + buffer.append(iText.substring(lastAppend)); + return buffer.toString(); + } + + /** + * Like String.startsWith() but ignoring case + */ + public static boolean startsWithIgnoreCase(final String iText, final String iToFind) { + if (iText.length() < iToFind.length()) + return false; + + return iText.substring(0, iToFind.length()).equalsIgnoreCase(iToFind); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OSystemVariableResolver.java b/core/src/main/java/com/orientechnologies/common/parser/OSystemVariableResolver.java new file mode 100644 index 00000000000..2e1d6b98fd3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OSystemVariableResolver.java @@ -0,0 +1,106 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.parser; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Resolve system variables embedded in a String. + * + * @author Luca Garulli (luca.garulli--at--assetdata.it) + * + */ +public class OSystemVariableResolver implements OVariableParserListener { + public static final String VAR_BEGIN = "${"; + public static final String VAR_END = "}"; + + private static OSystemVariableResolver instance = new OSystemVariableResolver(); + + public static String resolveSystemVariables(final String iPath) { + return resolveSystemVariables(iPath, null); + } + + public static String resolveSystemVariables(final String iPath, final String iDefault) { + if (iPath == null) + return iDefault; + + return (String) OVariableParser.resolveVariables(iPath, VAR_BEGIN, VAR_END, instance, iDefault); + } + + public static String resolveVariable(final String variable) { + if (variable == null) + return null; + + String resolved = System.getProperty(variable); + + if (resolved == null) + // TRY TO FIND THE VARIABLE BETWEEN SYSTEM'S ENVIRONMENT PROPERTIES + resolved = System.getenv(variable); + + return resolved; + } + + @Override + public String resolve(final String variable) { + return resolveVariable(variable); + } + + public static void setEnv(final String name, String value) { + final Map map = new HashMap(System.getenv()); + map.put(name, value); + setEnv(map); + } + + public static void setEnv(final Map newenv) { + try { + Class processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); + Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); + theEnvironmentField.setAccessible(true); + Map env = (Map) theEnvironmentField.get(null); + env.putAll(newenv); + Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); + theCaseInsensitiveEnvironmentField.setAccessible(true); + Map cienv = (Map) theCaseInsensitiveEnvironmentField.get(null); + cienv.putAll(newenv); + } catch (NoSuchFieldException e) { + try { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (Class cl : classes) { + if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Object obj = field.get(env); + Map map = (Map) obj; + map.clear(); + map.putAll(newenv); + } + } + } catch (Exception e2) { + e2.printStackTrace(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OVariableParser.java b/core/src/main/java/com/orientechnologies/common/parser/OVariableParser.java new file mode 100644 index 00000000000..0459291f75f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OVariableParser.java @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.parser; + +import com.orientechnologies.common.log.OLogManager; + +/** + * Resolve entity class and descriptors using the paths configured. + * + * @author Luca Garulli (luca.garulli--at--assetdata.it) + */ +public class OVariableParser { + public static Object resolveVariables(final String iText, final String iBegin, final String iEnd, + final OVariableParserListener iListener) { + return resolveVariables(iText, iBegin, iEnd, iListener, null); + } + + public static Object resolveVariables(final String iText, final String iBegin, final String iEnd, + final OVariableParserListener iListener, final Object iDefaultValue) { + if (iListener == null) + throw new IllegalArgumentException("Missed VariableParserListener listener"); + + int beginPos = iText.lastIndexOf(iBegin); + if (beginPos == -1) + return iText; + + int endPos = iText.indexOf(iEnd, beginPos + 1); + if (endPos == -1) + return iText; + + String pre = iText.substring(0, beginPos); + String var = iText.substring(beginPos + iBegin.length(), endPos); + String post = iText.substring(endPos + iEnd.length()); + + Object resolved = iListener.resolve(var); + + if (resolved == null) { + if (iDefaultValue == null) + OLogManager.instance().info(null, "[OVariableParser.resolveVariables] Error on resolving property: %s", var); + else + resolved = iDefaultValue; + } + + if (pre.length() > 0 || post.length() > 0) { + final String path = pre + (resolved != null ? resolved.toString() : "") + post; + return resolveVariables(path, iBegin, iEnd, iListener); + } + + return resolved; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/parser/OVariableParserListener.java b/core/src/main/java/com/orientechnologies/common/parser/OVariableParserListener.java new file mode 100644 index 00000000000..f2d43c65015 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/parser/OVariableParserListener.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.parser; + +/** + * Wake up at every variable found. + * + * @author Luca Garulli (luca.garulli--at--assetdata.it + * + */ +public interface OVariableParserListener { + Object resolve(String iVariable); +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/AtomicLongOProfilerHookValue.java b/core/src/main/java/com/orientechnologies/common/profiler/AtomicLongOProfilerHookValue.java new file mode 100644 index 00000000000..456f1f4f421 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/AtomicLongOProfilerHookValue.java @@ -0,0 +1,19 @@ +package com.orientechnologies.common.profiler; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by tglman on 03/05/17. + */ +public class AtomicLongOProfilerHookValue implements OAbstractProfiler.OProfilerHookValue { + private final AtomicLong value; + + public AtomicLongOProfilerHookValue(AtomicLong value) { + this.value = value; + } + + @Override + public Object getValue() { + return value.get(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OAbstractProfiler.java b/core/src/main/java/com/orientechnologies/common/profiler/OAbstractProfiler.java new file mode 100755 index 00000000000..cdeedf3f6d7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OAbstractProfiler.java @@ -0,0 +1,456 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.profiler; + +import com.orientechnologies.common.concur.resource.OSharedResourceAbstract; +import com.orientechnologies.common.io.OFileUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.cache.OReadCache; +import com.orientechnologies.orient.core.storage.cache.OWriteCache; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import java.io.File; +import java.io.PrintStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class OAbstractProfiler extends OSharedResourceAbstract + implements OProfiler, OOrientStartupListener, OProfilerMXBean { + + protected final Map hooks = new ConcurrentHashMap(); + protected final ConcurrentHashMap dictionary = new ConcurrentHashMap(); + protected final ConcurrentHashMap types = new ConcurrentHashMap(); + protected long recordingFrom = -1; + protected TimerTask autoDumpTask; + protected List listeners = new ArrayList(); + + public interface OProfilerHookValue { + Object getValue(); + } + + public class OProfilerHookRuntime { + public OProfilerHookValue hook; + public METRIC_TYPE type; + + public OProfilerHookRuntime(final OProfilerHookValue hook, final METRIC_TYPE type) { + this.hook = hook; + this.type = type; + } + } + + public class OProfilerHookStatic { + public Object value; + public METRIC_TYPE type; + + public OProfilerHookStatic(final Object value, final METRIC_TYPE type) { + this.value = value; + this.type = type; + } + } + + private static final class MemoryChecker extends TimerTask { + @Override + public void run() { + try { + final long jvmTotMemory = Runtime.getRuntime().totalMemory(); + final long jvmMaxMemory = Runtime.getRuntime().maxMemory(); + + for (OStorage s : Orient.instance().getStorages()) { + if (s instanceof OLocalPaginatedStorage) { + final OReadCache dk = ((OLocalPaginatedStorage) s).getReadCache(); + final OWriteCache wk = ((OLocalPaginatedStorage) s).getWriteCache(); + if (dk == null || wk == null) + // NOT YET READY + continue; + + final long totalDiskCacheUsedMemory = (dk.getUsedMemory() + wk.getExclusiveWriteCachePagesSize()) / OFileUtils.MEGABYTE; + final long maxDiskCacheUsedMemory = OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong(); + + // CHECK IF THERE IS MORE THAN 40% HEAP UNUSED AND DISK-CACHE IS 80% OF THE MAXIMUM SIZE + if ((jvmTotMemory * 140 / 100) < jvmMaxMemory && (totalDiskCacheUsedMemory * 120 / 100) > maxDiskCacheUsedMemory) { + + final long suggestedMaxHeap = jvmTotMemory * 120 / 100; + final long suggestedDiskCache = + OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() + (jvmMaxMemory - suggestedMaxHeap) / OFileUtils.MEGABYTE; + + OLogManager.instance().info(this, + "Database '%s' uses %,dMB/%,dMB of DISKCACHE memory, while Heap is not completely used (usedHeap=%dMB maxHeap=%dMB). To improve performance set maxHeap to %dMB and DISKCACHE to %dMB", + s.getName(), totalDiskCacheUsedMemory, maxDiskCacheUsedMemory, jvmTotMemory / OFileUtils.MEGABYTE, + jvmMaxMemory / OFileUtils.MEGABYTE, suggestedMaxHeap / OFileUtils.MEGABYTE, suggestedDiskCache); + + OLogManager.instance().info(this, + "-> Open server.sh (or server.bat on Windows) and change the following variables: 1) MAXHEAP=-Xmx%dM 2) MAXDISKCACHE=%d", + suggestedMaxHeap / OFileUtils.MEGABYTE, suggestedDiskCache); + } + } + } + } catch (Throwable e) { + OLogManager.instance().debug(this, "Error on memory checker task", e); + } + } + } + + public OAbstractProfiler() { + Orient.instance().registerWeakOrientStartupListener(this); + } + + public OAbstractProfiler(final OAbstractProfiler profiler) { + hooks.putAll(profiler.hooks); + dictionary.putAll(profiler.dictionary); + types.putAll(profiler.types); + + Orient.instance().registerWeakOrientStartupListener(this); + } + + protected abstract void setTip(String iMessage, AtomicInteger counter); + + protected abstract AtomicInteger getTip(String iMessage); + + public abstract boolean isEnterpriseEdition(); + + public static String dumpEnvironment() { + final StringBuilder buffer = new StringBuilder(); + + final Runtime runtime = Runtime.getRuntime(); + + final long freeSpaceInMB = new File(".").getFreeSpace(); + final long totalSpaceInMB = new File(".").getTotalSpace(); + + int stgs = 0; + long diskCacheUsed = 0; + long diskCacheTotal = 0; + for (OStorage stg : Orient.instance().getStorages()) { + if (stg instanceof OLocalPaginatedStorage) { + diskCacheUsed += ((OLocalPaginatedStorage) stg).getReadCache().getUsedMemory(); + diskCacheTotal += OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024; + stgs++; + } + } + try { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName osMBeanName = ObjectName.getInstance(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME); + if (mbs.isInstanceOf(osMBeanName, "com.sun.management.OperatingSystemMXBean")) { + final long osTotalMem = ((Number) mbs.getAttribute(osMBeanName, "TotalPhysicalMemorySize")).longValue(); + final long osUsedMem = osTotalMem - ((Number) mbs.getAttribute(osMBeanName, "FreePhysicalMemorySize")).longValue(); + + buffer.append(String + .format("OrientDB Memory profiler: HEAP=%s of %s - DISKCACHE (%s dbs)=%s of %s - OS=%s of %s - FS=%s of %s", + OFileUtils.getSizeAsString(runtime.totalMemory() - runtime.freeMemory()), + OFileUtils.getSizeAsString(runtime.maxMemory()), stgs, OFileUtils.getSizeAsString(diskCacheUsed), + OFileUtils.getSizeAsString(diskCacheTotal), OFileUtils.getSizeAsString(osUsedMem), + OFileUtils.getSizeAsString(osTotalMem), OFileUtils.getSizeAsString(freeSpaceInMB), + OFileUtils.getSizeAsString(totalSpaceInMB))); + return buffer.toString(); + } + } catch (Exception e) { + // Nothing to do. Proceed with default output + } + + buffer.append(String.format("OrientDB Memory profiler: Heap=%s of %s - DiskCache (%s dbs)=%s of %s - FS=%s of %s", + OFileUtils.getSizeAsString(runtime.totalMemory() - runtime.freeMemory()), OFileUtils.getSizeAsString(runtime.maxMemory()), + stgs, OFileUtils.getSizeAsString(diskCacheUsed), OFileUtils.getSizeAsString(diskCacheTotal), + OFileUtils.getSizeAsString(freeSpaceInMB), OFileUtils.getSizeAsString(totalSpaceInMB))); + + return buffer.toString(); + } + + @Override + public void onStartup() { + if (OGlobalConfiguration.PROFILER_ENABLED.getValueAsBoolean()) + // ACTIVATE RECORDING OF THE PROFILER + startRecording(); + installMemoryChecker(); + } + + public void shutdown() { + stopRecording(); + } + + public int reportTip(final String iMessage) { + AtomicInteger counter = getTip(iMessage); + if (counter == null) { + // DUMP THE MESSAGE ONLY THE FIRST TIME + OLogManager.instance().info(this, "[TIP] " + iMessage); + + counter = new AtomicInteger(0); + } + + setTip(iMessage, counter); + + return counter.incrementAndGet(); + } + + public boolean startRecording() { + if (isRecording()) + return false; + + recordingFrom = System.currentTimeMillis(); + return true; + } + + public boolean stopRecording() { + if (!isRecording()) + return false; + + recordingFrom = -1; + return true; + } + + public boolean isRecording() { + return recordingFrom > -1; + } + + public void updateCounter(final String iStatName, final String iDescription, final long iPlus) { + updateCounter(iStatName, iDescription, iPlus, iStatName); + } + + @Override + public String getName() { + return "profiler"; + } + + @Override + public void startup() { + startRecording(); + } + + @Override + public String dump() { + return dumpEnvironment(); + } + + @Override + public void dump(final PrintStream out) { + out.println(dumpEnvironment()); + } + + @Override + public String dumpCounters() { + return null; + } + + @Override + public OProfilerEntry getChrono(String string) { + return null; + } + + @Override + public long startChrono() { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime) { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary) { + return 0; + } + + @Override + public String dumpChronos() { + return null; + } + + @Override + public String[] getCountersAsString() { + return null; + } + + @Override + public String[] getChronosAsString() { + return null; + } + + @Override + public Date getLastReset() { + return null; + } + + @Override + public void setAutoDump(final int iSeconds) { + if (autoDumpTask != null) { + // CANCEL ANY PREVIOUS RUNNING TASK + autoDumpTask.cancel(); + autoDumpTask = null; + } + + if (iSeconds > 0) { + OLogManager.instance().info(this, "Enabled auto dump of profiler every %d second(s)", iSeconds); + + final int ms = iSeconds * 1000; + + autoDumpTask = new TimerTask() { + + @Override + public void run() { + final StringBuilder output = new StringBuilder(); + + output.append( + "\n*******************************************************************************************************************************************"); + output.append("\nPROFILER AUTO DUMP OUTPUT (to disabled it set 'profiler.autoDump.interval' = 0):\n"); + output.append(dump()); + output.append( + "\n*******************************************************************************************************************************************"); + + OLogManager.instance().info(null, output.toString()); + } + }; + + Orient.instance().scheduleTask(autoDumpTask, ms, ms); + } else + OLogManager.instance().info(this, "Auto dump of profiler disabled", iSeconds); + + } + + @Override + public String metadataToJSON() { + return null; + } + + @Override + public Map> getMetadata() { + final Map> metadata = new HashMap>(); + for (Entry entry : dictionary.entrySet()) + metadata.put(entry.getKey(), new OPair(entry.getValue(), types.get(entry.getKey()))); + return metadata; + } + + public void registerHookValue(final String iName, final String iDescription, final METRIC_TYPE iType, + final OProfilerHookValue iHookValue) { + registerHookValue(iName, iDescription, iType, iHookValue, iName); + } + + public void registerHookValue(final String iName, final String iDescription, final METRIC_TYPE iType, + final OProfilerHookValue iHookValue, final String iMetadataName) { + if (iName != null) { + unregisterHookValue(iName); + updateMetadata(iMetadataName, iDescription, iType); + hooks.put(iName, new OProfilerHookRuntime(iHookValue, iType)); + } + } + + @Override + public void unregisterHookValue(final String iName) { + if (iName != null) + hooks.remove(iName); + } + + @Override + public String getSystemMetric(final String iMetricName) { + final StringBuilder buffer = new StringBuilder("system.".length() + iMetricName.length() + 1); + buffer.append("system."); + buffer.append(iMetricName); + return buffer.toString(); + } + + @Override + public String getProcessMetric(final String iMetricName) { + final StringBuilder buffer = new StringBuilder("process.".length() + iMetricName.length() + 1); + buffer.append("process."); + buffer.append(iMetricName); + return buffer.toString(); + } + + @Override + public String getDatabaseMetric(final String iDatabaseName, final String iMetricName) { + final StringBuilder buffer = new StringBuilder(128); + buffer.append("db."); + buffer.append(iDatabaseName != null ? iDatabaseName : "*"); + buffer.append('.'); + buffer.append(iMetricName); + return buffer.toString(); + } + + @Override + public String toJSON(String command, final String iPar1) { + return null; + } + + protected void installMemoryChecker() { + final long memoryCheckInterval = OGlobalConfiguration.PROFILER_MEMORYCHECK_INTERVAL.getValueAsLong(); + + if (memoryCheckInterval > 0) + Orient.instance().scheduleTask(new MemoryChecker(), memoryCheckInterval, memoryCheckInterval); + } + + /** + * Updates the metric metadata. + */ + protected void updateMetadata(final String iName, final String iDescription, final METRIC_TYPE iType) { + if (iDescription != null && dictionary.putIfAbsent(iName, iDescription) == null) + types.put(iName, iType); + } + + @Override + public void registerListener(OProfilerListener listener) { + listeners.add(listener); + } + + @Override + public void unregisterListener(OProfilerListener listener) { + listeners.remove(listener); + } + + @Override + public String threadDump() { + final StringBuilder dump = new StringBuilder(); + dump.append("THREAD DUMP\n"); + final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100); + for (ThreadInfo threadInfo : threadInfos) { + dump.append('"'); + dump.append(threadInfo.getThreadName()); + dump.append("\" id="); + dump.append(threadInfo.getThreadId()); + dump.append(" "); + final Thread.State state = threadInfo.getThreadState(); + dump.append("\n java.lang.Thread.State: "); + dump.append(state); + final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace(); + for (final StackTraceElement stackTraceElement : stackTraceElements) { + dump.append("\n at "); + dump.append(stackTraceElement); + } + dump.append("\n\n"); + } + return dump.toString(); + } + + @Override + public METRIC_TYPE getType(final String k) { + return types.get(k); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OProfiler.java b/core/src/main/java/com/orientechnologies/common/profiler/OProfiler.java new file mode 100755 index 00000000000..3c487d331e1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OProfiler.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.profiler; + +import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.common.util.OService; + +import javax.annotation.CheckReturnValue; +import javax.annotation.meta.When; +import java.io.PrintStream; +import java.util.Date; +import java.util.Map; + +public interface OProfiler extends OService { + + enum METRIC_TYPE { + CHRONO, COUNTER, STAT, SIZE, ENABLED, TEXT + } + + METRIC_TYPE getType(String k); + + void updateCounter(String iStatName, String iDescription, long iPlus); + + void updateCounter(String iStatName, String iDescription, long iPlus, String iDictionary); + + long getCounter(String iStatName); + + String dump(); + + String dumpCounters(); + + OProfilerEntry getChrono(String string); + + long startChrono(); + + @CheckReturnValue(when = When.NEVER) + long stopChrono(String iName, String iDescription, long iStartTime); + + @CheckReturnValue(when = When.NEVER) + long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary); + + @CheckReturnValue(when = When.NEVER) + long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary, String payload); + + @CheckReturnValue(when = When.NEVER) + long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary, String payload, String user); + + String dumpChronos(); + + String[] getCountersAsString(); + + String[] getChronosAsString(); + + Date getLastReset(); + + boolean isRecording(); + + boolean startRecording(); + + boolean stopRecording(); + + void unregisterHookValue(String string); + + void configure(String string); + + void setAutoDump(int iNewValue); + + String metadataToJSON(); + + Map> getMetadata(); + + void registerHookValue(String iName, String iDescription, METRIC_TYPE iType, OProfilerHookValue iHookValue); + + void registerHookValue(String iName, String iDescription, METRIC_TYPE iType, OProfilerHookValue iHookValue, String iMetadataName); + + String getSystemMetric(String iMetricName); + + String getProcessMetric(String iName); + + String getDatabaseMetric(String databaseName, String iName); + + String toJSON(String command, String iPar1); + + void resetRealtime(String iText); + + void dump(PrintStream out); + + int reportTip(String iMessage); + + void registerListener(OProfilerListener listener); + + void unregisterListener(OProfilerListener listener); + + String threadDump(); + + boolean isEnterpriseEdition(); +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OProfilerEntry.java b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerEntry.java new file mode 100644 index 00000000000..87a71850b63 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerEntry.java @@ -0,0 +1,117 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.profiler; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** + * Contains the profiling data abount timing. + * + * @author Luca Garulli + */ +public class OProfilerEntry { + public String name = null; + public long entries = 0; + public long last = 0; + public long min = 999999999; + public long max = 0; + public float average = 0; + public long total = 0; + public final long firstExecution; + public long lastExecution; + + public String payLoad; + public String description; + + public long lastResetEntries = 0; + public long lastReset; + + public Set users = new HashSet(); + + public OProfilerEntry() { + firstExecution = System.currentTimeMillis(); + lastExecution = firstExecution; + } + + public void updateLastExecution() { + lastExecution = System.currentTimeMillis(); + } + + public ODocument toDocument() { + final ODocument doc = new ODocument(); + doc.field("entries", entries); + doc.field("last", last); + doc.field("min", min); + doc.field("max", max); + doc.field("average", average); + doc.field("total", total); + doc.field("firstExecution", firstExecution); + doc.field("lastExecution", lastExecution); + doc.field("lastReset", lastReset); + doc.field("lastResetEntries", lastResetEntries); + if (payLoad != null) + doc.field("payload", payLoad); + return doc; + } + + public String toJSON() { + final StringBuilder buffer = new StringBuilder(1024); + toJSON(buffer); + return buffer.toString(); + } + + public void toJSON(final StringBuilder buffer) { + buffer.append('{'); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "entries", entries)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "last", last)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "min", min)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "max", max)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%.2f,", "average", average)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "total", total)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "firstExecution", firstExecution)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "lastExecution", lastExecution)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "lastReset", lastReset)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\":%d,", "lastResetEntries,", lastResetEntries)); + if (payLoad != null) + buffer.append(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "payload,", payLoad)); + buffer.append(String.format(Locale.ENGLISH, "\"%s\": [", "users")); + + String usersList = ""; + int i = 0; + for (String user : users) { + buffer.append(String.format(Locale.ENGLISH, "%s\"%s\"", (i > 0) ? "," : "", user)); + i++; + } + buffer.append(String.format(Locale.ENGLISH, "%s", usersList)); + + buffer.append(String.format(Locale.ENGLISH, "]")); + buffer.append('}'); + } + + @Override + public String toString() { + return String.format("Profiler entry [%s]: total=%d, average=%.2f, items=%d, last=%d, max=%d, min=%d", name, total, average, + entries, last, max, min); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OProfilerListener.java b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerListener.java new file mode 100644 index 00000000000..c13ffb088eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerListener.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.profiler; + +/** + * Created by Enrico Risa on 23/11/15. + */ +public interface OProfilerListener { + + public void onUpdateCounter(String iName, long counter, long recordingFrom, long recordingTo); + + public void onUpdateChrono(OProfilerEntry chrono); + + public void onSnapshotCreated(Object snapshot); +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OProfilerMXBean.java b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerMXBean.java new file mode 100755 index 00000000000..fe559cb4fde --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerMXBean.java @@ -0,0 +1,40 @@ +package com.orientechnologies.common.profiler; + +import com.orientechnologies.common.util.OPair; + +import java.util.Date; +import java.util.Map; + +public interface OProfilerMXBean { + long getCounter(String iStatName); + + String dump(); + + String dumpCounters(); + + String dumpChronos(); + + String[] getCountersAsString(); + + String[] getChronosAsString(); + + Date getLastReset(); + + boolean isRecording(); + + boolean startRecording(); + + boolean stopRecording(); + + void setAutoDump(int iNewValue); + + String metadataToJSON(); + + String getSystemMetric(String iMetricName); + + String getProcessMetric(String iName); + + String getDatabaseMetric(String databaseName, String iName); + + void resetRealtime(String iText); +} diff --git a/core/src/main/java/com/orientechnologies/common/profiler/OProfilerStub.java b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerStub.java new file mode 100755 index 00000000000..9f599da48b7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/profiler/OProfilerStub.java @@ -0,0 +1,233 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.profiler; + +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.orientechnologies.orient.core.config.OGlobalConfiguration.PROFILER_MAXVALUES; + +public class OProfilerStub extends OAbstractProfiler { + + protected ConcurrentMap counters = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity( + PROFILER_MAXVALUES.getValueAsInteger()).build(); + private ConcurrentLinkedHashMap tips = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity( + PROFILER_MAXVALUES.getValueAsInteger()).build(); + private ConcurrentLinkedHashMap tipsTimestamp = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity( + PROFILER_MAXVALUES.getValueAsInteger()).build(); + + public OProfilerStub() { + } + + public OProfilerStub(final OAbstractProfiler profiler) { + super(profiler); + } + + @Override + protected void setTip(final String iMessage, final AtomicInteger counter) { + tips.put(iMessage, counter); + tipsTimestamp.put(iMessage, System.currentTimeMillis()); + } + + @Override + protected AtomicInteger getTip(final String iMessage) { + return tips.get(iMessage); + } + + @Override + public boolean isEnterpriseEdition() { + return false; + } + + public void configure(final String iConfiguration) { + if (iConfiguration == null || iConfiguration.length() == 0) + return; + + if (isRecording()) + stopRecording(); + + startRecording(); + } + + public boolean startRecording() { + counters = new ConcurrentLinkedHashMap.Builder() + .maximumWeightedCapacity(PROFILER_MAXVALUES.getValueAsInteger()).build(); + tips = new ConcurrentLinkedHashMap.Builder() + .maximumWeightedCapacity(PROFILER_MAXVALUES.getValueAsInteger()).build(); + tipsTimestamp = new ConcurrentLinkedHashMap.Builder() + .maximumWeightedCapacity(PROFILER_MAXVALUES.getValueAsInteger()).build(); + + if (super.startRecording()) { + counters.clear(); + return true; + } + return false; + } + + public boolean stopRecording() { + if (super.stopRecording()) { + counters.clear(); + return true; + } + return false; + } + + @Override + public String dump() { + if (recordingFrom < 0) + return ""; + + final StringBuilder buffer = new StringBuilder(super.dump()); + + if (tips.size() == 0) + return ""; + + buffer.append("TIPS:"); + + buffer.append(String.format("\n%100s +------------+", "")); + buffer.append(String.format("\n%100s | Value |", "Name")); + buffer.append(String.format("\n%100s +------------+", "")); + + final List names = new ArrayList(tips.keySet()); + Collections.sort(names); + + for (String n : names) { + final AtomicInteger v = tips.get(n); + buffer.append(String.format("\n%-100s | %10d |", n, v.intValue())); + } + + buffer.append(String.format("\n%100s +------------+", "")); + return buffer.toString(); + } + + public void updateCounter(final String statName, final String description, final long plus, final String metadata) { + if (statName == null || !isRecording()) + return; + + Long oldValue; + Long newValue; + do { + oldValue = counters.get(statName); + + if (oldValue == null) { + counters.putIfAbsent(statName, 0L); + oldValue = counters.get(statName); + } + + newValue = oldValue + plus; + } while (!counters.replace(statName, oldValue, newValue)); + } + + public long getCounter(final String statName) { + if (statName == null || !isRecording()) + return -1; + + final Long stat = counters.get(statName); + if (stat == null) + return -1; + + return stat; + } + + @Override + public String dumpCounters() { + return null; + } + + @Override + public OProfilerEntry getChrono(String string) { + return null; + } + + @Override + public long startChrono() { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime) { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary) { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary, String payload) { + return 0; + } + + @Override + public long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary, String payload, String user) { + return 0; + } + + @Override + public String dumpChronos() { + return null; + } + + @Override + public String[] getCountersAsString() { + return null; + } + + @Override + public String[] getChronosAsString() { + return null; + } + + @Override + public Date getLastReset() { + return null; + } + + @Override + public String metadataToJSON() { + return null; + } + + @Override + public String toJSON(String command, final String iPar1) { + return null; + } + + @Override + public void resetRealtime(String iText) { + } + + /** + * Updates the metric metadata. + */ + protected void updateMetadata(final String iName, final String iDescription, final METRIC_TYPE iType) { + if (iDescription != null && dictionary.putIfAbsent(iName, iDescription) == null) + types.put(iName, iType); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/reflection/OReflectionHelper.java b/core/src/main/java/com/orientechnologies/common/reflection/OReflectionHelper.java new file mode 100755 index 00000000000..b43224fa7de --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/reflection/OReflectionHelper.java @@ -0,0 +1,246 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.reflection; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.orientechnologies.common.log.OLogManager; + +/** + * Helper class to browse .class files. See also: http://forums.sun.com/thread.jspa?threadID=341935&start=15&tstart=0 + * + * @author Antony Stubbs + */ +public class OReflectionHelper { + private static final String CLASS_EXTENSION = ".class"; + + public static List> getClassesFor(final Collection classNames, final ClassLoader classLoader) + throws ClassNotFoundException { + List> classes = new ArrayList>(classNames.size()); + for (String className : classNames) { + classes.add(Class.forName(className, true, classLoader)); + } + return classes; + } + + public static List> getClassesFor(final String iPackageName, final ClassLoader iClassLoader) + throws ClassNotFoundException { + // This will hold a list of directories matching the pckgname. + // There may be more than one if a package is split over multiple jars/paths + final List> classes = new ArrayList>(); + final ArrayList directories = new ArrayList(); + try { + // Ask for all resources for the path + final String packageUrl = iPackageName.replace('.', '/'); + Enumeration resources = iClassLoader.getResources(packageUrl); + if (!resources.hasMoreElements()) { + resources = iClassLoader.getResources(packageUrl + CLASS_EXTENSION); + if (resources.hasMoreElements()) { + throw new IllegalArgumentException(iPackageName + " does not appear to be a valid package but a class"); + } + } else { + while (resources.hasMoreElements()) { + final URL res = resources.nextElement(); + if (res.getProtocol().equalsIgnoreCase("jar")) { + final JarURLConnection conn = (JarURLConnection) res.openConnection(); + final JarFile jar = conn.getJarFile(); + for (JarEntry e : Collections.list(jar.entries())) { + + if (e.getName().startsWith(iPackageName.replace('.', '/')) && e.getName().endsWith(CLASS_EXTENSION) + && !e.getName().contains("$")) { + final String className = e.getName().replace("/", ".").substring(0, e.getName().length() - 6); + classes.add(Class.forName(className, true, iClassLoader)); + } + } + } else + directories.add(new File(URLDecoder.decode(res.getPath(), "UTF-8"))); + } + } + } catch (NullPointerException x) { + throw new ClassNotFoundException(iPackageName + " does not appear to be " + "a valid package (Null pointer exception)", x); + } catch (UnsupportedEncodingException encex) { + throw new ClassNotFoundException(iPackageName + " does not appear to be " + "a valid package (Unsupported encoding)", encex); + } catch (IOException ioex) { + throw new ClassNotFoundException("IOException was thrown when trying " + "to get all resources for " + iPackageName, ioex); + } + + // For every directory identified capture all the .class files + for (File directory : directories) { + if (directory.exists()) { + // Get the list of the files contained in the package + File[] files = directory.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + classes.addAll(findClasses(file, iPackageName, iClassLoader)); + } else { + String className; + if (file.getName().endsWith(CLASS_EXTENSION)) { + className = file.getName().substring(0, file.getName().length() - CLASS_EXTENSION.length()); + classes.add(Class.forName(iPackageName + '.' + className, true, iClassLoader)); + } + } + } + } else { + throw new ClassNotFoundException(iPackageName + " (" + directory.getPath() + ") does not appear to be a valid package"); + } + } + return classes; + } + + /** + * Recursive method used to find all classes in a given directory and subdirs. + * + * @param iDirectory + * The base directory + * @param iPackageName + * The package name for classes found inside the base directory + * @return The classes + * @throws ClassNotFoundException + */ + private static List> findClasses(final File iDirectory, String iPackageName, ClassLoader iClassLoader) + throws ClassNotFoundException { + final List> classes = new ArrayList>(); + if (!iDirectory.exists()) + return classes; + + iPackageName += "." + iDirectory.getName(); + + String className; + final File[] files = iDirectory.listFiles(); + if (files != null) + for (File file : files) { + if (file.isDirectory()) { + if (file.getName().contains(".")) + continue; + classes.addAll(findClasses(file, iPackageName, iClassLoader)); + } else if (file.getName().endsWith(CLASS_EXTENSION)) { + className = file.getName().substring(0, file.getName().length() - CLASS_EXTENSION.length()); + classes.add(Class.forName(iPackageName + '.' + className, true, iClassLoader)); + } + } + return classes; + } + + /** + * Filters discovered classes to see if they implement a given interface. + * + * @param thePackage + * @param theInterface + * @param iClassLoader + * @return The list of classes that implements the requested interface + */ + public static List> getClassessOfInterface(String thePackage, Class theInterface, final ClassLoader iClassLoader) { + List> classList = new ArrayList>(); + try { + for (Class discovered : getClassesFor(thePackage, iClassLoader)) { + if (Arrays.asList(discovered.getInterfaces()).contains(theInterface)) { + classList.add(discovered); + } + } + } catch (ClassNotFoundException ex) { + OLogManager.instance().error(null, "Error finding classes", ex); + } + + return classList; + } + + /** + * Returns the declared generic types of a class. + * + * @param iClass + * Class to examine + * @return The array of Type if any, otherwise null + */ + public static Type[] getGenericTypes(final Class iClass) { + final Type genericType = iClass.getGenericInterfaces()[0]; + if (genericType != null && genericType instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) genericType; + if (pt.getActualTypeArguments() != null && pt.getActualTypeArguments().length > 1) + return pt.getActualTypeArguments(); + } + return null; + } + + /** + * Returns the generic class of multi-value objects. + * + * @param p + * Field to examine + * @return The Class of generic type if any, otherwise null + */ + public static Class getGenericMultivalueType(final Field p) { + if (p.getType() instanceof Class) { + final Type genericType = p.getGenericType(); + if (genericType != null && genericType instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) genericType; + if (pt.getActualTypeArguments() != null && pt.getActualTypeArguments().length > 0) { + if (((Class) pt.getRawType()).isAssignableFrom(Map.class)) { + if (pt.getActualTypeArguments()[1] instanceof Class) { + return (Class) pt.getActualTypeArguments()[1]; + } else if (pt.getActualTypeArguments()[1] instanceof ParameterizedType) + return (Class) ((ParameterizedType) pt.getActualTypeArguments()[1]).getRawType(); + } else if (pt.getActualTypeArguments()[0] instanceof Class) { + return (Class) pt.getActualTypeArguments()[0]; + } else if (pt.getActualTypeArguments()[0] instanceof ParameterizedType) + return (Class) ((ParameterizedType) pt.getActualTypeArguments()[0]).getRawType(); + } + } else if (p.getType().isArray()) + return p.getType().getComponentType(); + } + return null; + } + + /** + * Checks if a class is a Java type: Map, Collection,arrays, Number (extensions and primitives), String, Boolean.. + * + * @param clazz + * Class to examine + * @return true if clazz is Java type, false otherwise + */ + public static boolean isJavaType(Class clazz) { + if (clazz.isPrimitive()) + return true; + else if (clazz.getName().startsWith("java.lang")) + return true; + else if (clazz.getName().startsWith("java.util")) + return true; + else if (clazz.isArray()) + return true; + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverter.java b/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverter.java new file mode 100755 index 00000000000..27cb0a746a3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverter.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.serialization; + +import java.nio.ByteOrder; + +/** + * @author Andrey Lomakin + * @since 26.07.12 + */ +public interface OBinaryConverter { + void putInt(byte[] buffer, int index, int value, ByteOrder byteOrder); + + int getInt(byte[] buffer, int index, ByteOrder byteOrder); + + void putShort(byte[] buffer, int index, short value, ByteOrder byteOrder); + + short getShort(byte[] buffer, int index, ByteOrder byteOrder); + + void putLong(byte[] buffer, int index, long value, ByteOrder byteOrder); + + long getLong(byte[] buffer, int index, ByteOrder byteOrder); + + void putChar(byte[] buffer, int index, char character, ByteOrder byteOrder); + + char getChar(byte[] buffer, int index, ByteOrder byteOrder); + + boolean nativeAccelerationUsed(); +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverterFactory.java b/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverterFactory.java new file mode 100755 index 00000000000..7b09b08b3fc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/OBinaryConverterFactory.java @@ -0,0 +1,53 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +/** + * @author Andrey Lomakin + * @since 26.07.12 + */ +public class OBinaryConverterFactory { + private static final boolean unsafeWasDetected; + + static { + boolean unsafeDetected = false; + + try { + Class sunClass = Class.forName("sun.misc.Unsafe"); + unsafeDetected = sunClass != null; + } catch (ClassNotFoundException cnfe) { + // Ignore + } + + unsafeWasDetected = unsafeDetected; + } + + public static OBinaryConverter getConverter() { + boolean useUnsafe = OGlobalConfiguration.MEMORY_USE_UNSAFE.getValueAsBoolean(); + + if (useUnsafe && unsafeWasDetected) + return OUnsafeBinaryConverter.INSTANCE; + + return OSafeBinaryConverter.INSTANCE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/OSafeBinaryConverter.java b/core/src/main/java/com/orientechnologies/common/serialization/OSafeBinaryConverter.java new file mode 100755 index 00000000000..0914cc4ac2e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/OSafeBinaryConverter.java @@ -0,0 +1,171 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization; + +import java.nio.ByteOrder; + +public class OSafeBinaryConverter implements OBinaryConverter { + public static final OSafeBinaryConverter INSTANCE = new OSafeBinaryConverter(); + + public void putShort(byte[] buffer, int index, short value, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + short2BytesBigEndian(value, buffer, index); + else + short2BytesLittleEndian(value, buffer, index); + } + + public short getShort(byte[] buffer, int index, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return bytes2ShortBigEndian(buffer, index); + + return bytes2ShortLittleEndian(buffer, index); + } + + public void putInt(byte[] buffer, int pointer, int value, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + int2BytesBigEndian(value, buffer, pointer); + else + int2BytesLittleEndian(value, buffer, pointer); + } + + public int getInt(byte[] buffer, int pointer, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return bytes2IntBigEndian(buffer, pointer); + + return bytes2IntLittleEndian(buffer, pointer); + } + + public void putLong(byte[] buffer, int index, long value, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + long2BytesBigEndian(value, buffer, index); + else + long2BytesLittleEndian(value, buffer, index); + } + + public long getLong(byte[] buffer, int index, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return bytes2LongBigEndian(buffer, index); + + return bytes2LongLittleEndian(buffer, index); + } + + public void putChar(byte[] buffer, int index, char character, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + buffer[index] = (byte) (character >>> 8); + buffer[index + 1] = (byte) character; + } else { + buffer[index + 1] = (byte) (character >>> 8); + buffer[index] = (byte) character; + } + + } + + public char getChar(byte[] buffer, int index, ByteOrder byteOrder) { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return (char) (((buffer[index] & 0xFF) << 8) + (buffer[index + 1] & 0xFF)); + + return (char) (((buffer[index + 1] & 0xFF) << 8) + (buffer[index] & 0xFF)); + } + + public boolean nativeAccelerationUsed() { + return false; + } + + private static byte[] short2BytesBigEndian(final short value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 1] = (byte) (value & 0xFF); + return b; + } + + private static byte[] short2BytesLittleEndian(final short value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset + 1] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset] = (byte) (value & 0xFF); + return b; + } + + private static short bytes2ShortBigEndian(final byte[] b, final int offset) { + return (short) ((b[offset] << 8) | (b[offset + 1] & 0xff)); + } + + private static short bytes2ShortLittleEndian(final byte[] b, final int offset) { + return (short) ((b[offset + 1] << 8) | (b[offset] & 0xff)); + } + + private static int bytes2IntBigEndian(final byte[] b, final int offset) { + return (b[offset]) << 24 | (0xff & b[offset + 1]) << 16 | (0xff & b[offset + 2]) << 8 | ((0xff & b[offset + 3])); + } + + private static int bytes2IntLittleEndian(final byte[] b, final int offset) { + return (b[offset + 3]) << 24 | (0xff & b[offset + 2]) << 16 | (0xff & b[offset + 1]) << 8 | ((0xff & b[offset])); + } + + private static byte[] int2BytesBigEndian(final int value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 3] = (byte) (value & 0xFF); + return b; + } + + private static byte[] int2BytesLittleEndian(final int value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset + 3] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset] = (byte) (value & 0xFF); + return b; + } + + private static byte[] long2BytesBigEndian(final long value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 56) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 48) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 40) & 0xFF); + b[iBeginOffset + 3] = (byte) ((value >>> 32) & 0xFF); + b[iBeginOffset + 4] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 5] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 6] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 7] = (byte) (value & 0xFF); + return b; + } + + private static byte[] long2BytesLittleEndian(final long value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset + 7] = (byte) ((value >>> 56) & 0xFF); + b[iBeginOffset + 6] = (byte) ((value >>> 48) & 0xFF); + b[iBeginOffset + 5] = (byte) ((value >>> 40) & 0xFF); + b[iBeginOffset + 4] = (byte) ((value >>> 32) & 0xFF); + b[iBeginOffset + 3] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset] = (byte) (value & 0xFF); + return b; + } + + private static long bytes2LongBigEndian(final byte[] b, final int offset) { + return ((0xff & b[offset + 7]) | (0xff & b[offset + 6]) << 8 | (0xff & b[offset + 5]) << 16 + | (long) (0xff & b[offset + 4]) << 24 | (long) (0xff & b[offset + 3]) << 32 | (long) (0xff & b[offset + 2]) << 40 + | (long) (0xff & b[offset + 1]) << 48 | (long) (0xff & b[offset]) << 56); + } + + private static long bytes2LongLittleEndian(final byte[] b, final int offset) { + return ((0xff & b[offset]) | (0xff & b[offset + 1]) << 8 | (0xff & b[offset + 2]) << 16 | (long) (0xff & b[offset + 3]) << 24 + | (long) (0xff & b[offset + 4]) << 32 | (long) (0xff & b[offset + 5]) << 40 | (long) (0xff & b[offset + 6]) << 48 | (long) (0xff & b[offset + 7]) << 56); + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/OUnsafeBinaryConverter.java b/core/src/main/java/com/orientechnologies/common/serialization/OUnsafeBinaryConverter.java new file mode 100755 index 00000000000..2b980b81329 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/OUnsafeBinaryConverter.java @@ -0,0 +1,243 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import sun.misc.Unsafe; + +/** + * @author Andrey Lomakin + * @since 26.07.12 + */ +@SuppressWarnings("restriction") +public class OUnsafeBinaryConverter implements OBinaryConverter { + public static final OUnsafeBinaryConverter INSTANCE = new OUnsafeBinaryConverter(); + + private static final Unsafe theUnsafe; + private static final long BYTE_ARRAY_OFFSET; + + private static final boolean useOnlyAlignedAccess = OGlobalConfiguration.DIRECT_MEMORY_ONLY_ALIGNED_ACCESS + .getValueAsBoolean(); + + static { + theUnsafe = (Unsafe) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + boolean wasAccessible = f.isAccessible(); + f.setAccessible(true); + try { + return f.get(null); + } finally { + f.setAccessible(wasAccessible); + } + + } catch (NoSuchFieldException e) { + throw new Error(e); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + }); + BYTE_ARRAY_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); + } + + public void putShort(byte[] buffer, int index, short value, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + if (!byteOrder.equals(ByteOrder.nativeOrder())) + value = Short.reverseBytes(value); + + theUnsafe.putShort(buffer, index + BYTE_ARRAY_OFFSET, value); + } else { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) (value >>> 8)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) value); + } else { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) value); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) (value >>> 8)); + } + } + } + + public short getShort(byte[] buffer, int index, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + short result = theUnsafe.getShort(buffer, index + BYTE_ARRAY_OFFSET); + if (!byteOrder.equals(ByteOrder.nativeOrder())) + result = Short.reverseBytes(result); + + return result; + } + + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return (short) ((theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET)) << 8 | (theUnsafe.getByte(buffer, index + + BYTE_ARRAY_OFFSET + 1) & 0xff)); + + return (short) ((theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET) & 0xff) | (theUnsafe.getByte(buffer, index + + BYTE_ARRAY_OFFSET + 1) << 8)); + } + + public void putInt(byte[] buffer, int pointer, int value, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + final long position = pointer + BYTE_ARRAY_OFFSET; + if (!byteOrder.equals(ByteOrder.nativeOrder())) + value = Integer.reverseBytes(value); + + theUnsafe.putInt(buffer, position, value); + } else { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET, (byte) (value >>> 24)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 1, (byte) (value >>> 16)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 2, (byte) (value >>> 8)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 3, (byte) (value)); + } else { + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET, (byte) (value)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 1, (byte) (value >>> 8)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 2, (byte) (value >>> 16)); + theUnsafe.putByte(buffer, pointer + BYTE_ARRAY_OFFSET + 3, (byte) (value >>> 24)); + } + } + } + + public int getInt(byte[] buffer, int pointer, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + final long position = pointer + BYTE_ARRAY_OFFSET; + int result = theUnsafe.getInt(buffer, position); + if (!byteOrder.equals(ByteOrder.nativeOrder())) + result = Integer.reverseBytes(result); + + return result; + } + + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + return ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET)) << 24) + | ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 1)) << 16) + | ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 2)) << 8) + | (0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 3)); + } + + return (0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET)) + | ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 1)) << 8) + | ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 2)) << 16) + | ((0xFF & theUnsafe.getByte(buffer, pointer + BYTE_ARRAY_OFFSET + 3)) << 24); + } + + public void putLong(byte[] buffer, int index, long value, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + if (!byteOrder.equals(ByteOrder.nativeOrder())) + value = Long.reverseBytes(value); + + theUnsafe.putLong(buffer, index + BYTE_ARRAY_OFFSET, value); + } else { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) (value >>> 56)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) (value >>> 48)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 2, (byte) (value >>> 40)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 3, (byte) (value >>> 32)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 4, (byte) (value >>> 24)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 5, (byte) (value >>> 16)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 6, (byte) (value >>> 8)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 7, (byte) (value)); + } else { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) (value)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) (value >>> 8)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 2, (byte) (value >>> 16)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 3, (byte) (value >>> 24)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 4, (byte) (value >>> 32)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 5, (byte) (value >>> 40)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 6, (byte) (value >>> 48)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 7, (byte) (value >>> 56)); + } + } + } + + public long getLong(byte[] buffer, int index, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + long result = theUnsafe.getLong(buffer, index + BYTE_ARRAY_OFFSET); + if (!byteOrder.equals(ByteOrder.nativeOrder())) + result = Long.reverseBytes(result); + + return result; + } + + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET)) << 56) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 1)) << 48) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 2)) << 40) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 3)) << 32) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 4)) << 24) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 5)) << 16) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 6)) << 8) + | (0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 7)); + + return (0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET)) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 1)) << 8) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 2)) << 16) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 3)) << 24) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 4)) << 32) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 5)) << 40) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 6)) << 48) + | ((0xFFL & theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET + 7)) << 56); + } + + public void putChar(byte[] buffer, int index, char character, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + if (!byteOrder.equals(ByteOrder.nativeOrder())) + character = Character.reverseBytes(character); + + theUnsafe.putChar(buffer, index + BYTE_ARRAY_OFFSET, character); + } else { + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) (character >>> 8)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) (character)); + } else { + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET, (byte) (character)); + theUnsafe.putByte(buffer, index + BYTE_ARRAY_OFFSET + 1, (byte) (character >>> 8)); + } + + } + } + + public char getChar(byte[] buffer, int index, ByteOrder byteOrder) { + if (!useOnlyAlignedAccess) { + char result = theUnsafe.getChar(buffer, index + BYTE_ARRAY_OFFSET); + if (!byteOrder.equals(ByteOrder.nativeOrder())) + result = Character.reverseBytes(result); + + return result; + } + + if (byteOrder.equals(ByteOrder.BIG_ENDIAN)) + return (char) ((theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET) << 8) | (theUnsafe.getByte(buffer, index + + BYTE_ARRAY_OFFSET + 1) & 0xff)); + + return (char) ((theUnsafe.getByte(buffer, index + BYTE_ARRAY_OFFSET) & 0xff) | (theUnsafe.getByte(buffer, index + + BYTE_ARRAY_OFFSET + 1) << 8)); + } + + public boolean nativeAccelerationUsed() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OBinarySerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OBinarySerializer.java new file mode 100644 index 00000000000..47bc1eec3b9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OBinarySerializer.java @@ -0,0 +1,238 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * This interface is used for serializing OrientDB datatypes in binary format. Serialized content is written into buffer that will + * contain not only given object presentation but all binary content. Such approach prevents creation of separate byte array for + * each object and decreased GC overhead. + * + * @author Evgeniy Degtiarenko (gmandnepr-at-gmail.com), Andrey Lomakin + */ +public interface OBinarySerializer { + + /** + * Obtain size of the serialized object Size is the amount of bites that required for storing object (for example: for storing + * integer we need 4 bytes) + * + * @param object is the object to measure its size + * @param hints List of parameters which may be used to choose appropriate serialization approach. + * @return size of the serialized object + */ + int getObjectSize(T object, Object... hints); + + /** + * Return size serialized presentation of given object. + * + * @param stream Serialized content. + * @param startPosition Position from which serialized presentation of given object is stored. + * @return Size serialized presentation of given object in bytes. + */ + int getObjectSize(byte[] stream, int startPosition); + + /** + * Writes object to the stream starting from the startPosition + * + * @param object is the object to serialize + * @param stream is the stream where object will be written + * @param startPosition + * @param hints List of parameters which may be used to choose appropriate serialization approach. + */ + void serialize(T object, byte[] stream, int startPosition, Object... hints); + + /** + * Reads object from the stream starting from the startPosition + * + * @param stream is the stream from object will be read + * @param startPosition is the position to start reading from + * @return instance of the deserialized object + */ + T deserialize(byte[] stream, int startPosition); + + /** + * @return Identifier of given serializer. + */ + byte getId(); + + /** + * @return true if binary presentation of object always has the same length. + */ + boolean isFixedLength(); + + /** + * @return Length of serialized data if {@link #isFixedLength()} method returns true. If {@link #isFixedLength()} + * method return false returned value is undefined. + */ + int getFixedLength(); + + /** + * Writes object to the stream starting from the startPosition using native acceleration. Serialized object presentation is + * platform dependant. + * + * @param object is the object to serialize + * @param stream is the stream where object will be written + * @param startPosition + * @param hints List of parameters which may be used to choose appropriate serialization approach. + */ + void serializeNativeObject(T object, byte[] stream, int startPosition, Object... hints); + + /** + * Reads object from the stream starting from the startPosition, in case there were serialized using + * {@link #serializeNativeObject(T, byte[], int, Object...)} method. + * + * @param stream is the stream from object will be read + * @param startPosition is the position to start reading from + * @return instance of the deserialized object + */ + T deserializeNativeObject(byte[] stream, int startPosition); + + /** + * Return size serialized presentation of given object, if it was serialized using + * {@link #serializeNativeObject(T, byte[], int, Object...)} method. + * + * @param stream Serialized content. + * @param startPosition Position from which serialized presentation of given object is stored. + * @return Size serialized presentation of given object in bytes. + */ + int getObjectSizeNative(byte[] stream, int startPosition); + + T preprocess(T value, Object... hints); + + /** + * Serializes binary presentation of object to {@link ByteBuffer}. + *

+ * Position of buffer should be set before calling of given method. + *

+ * Serialization result is compatible with result of call of {@link #serializeNativeObject(Object, byte[], int, Object...)} method. + * So if we call: + * + * buffer.position(10); + * binarySerializer.serializeInByteBufferObject(object, buffer); + * + *

+ * and then + * + * byte[] stream = new byte[serializedSize + 10]; + * buffer.position(10); + * buffer.get(stream); + * + *

+ * following assert should pass + *

+ * + * assert object.equals(binarySerializer.deserializeNativeObject(stream, 10)) + * + *

+ * Final position of ByteBuffer will be changed and will be equal to + * sum of buffer start position and value returned by method {@link #getObjectSize(Object, Object...)} + * + * @param object Object to serialize. + * @param buffer Buffer which will contain serialized presentation of buffer. + * @param hints Type (types in case of composite object) of object. + */ + void serializeInByteBufferObject(T object, ByteBuffer buffer, Object... hints); + + /** + * Converts binary presentation of object to object instance. + *

+ * Position of buffer should be set before call of this method. + * Binary format of method is expected to be the same as binary format of {@link #serializeNativeObject(Object, byte[], int, Object...)} + *

+ * So if we call + * + * byte[] stream = new byte[serializedSize]; + * binarySerializer.serializeNativeObject(object, stream, 0); + * + *

+ * following assert should pass + *

+ * + * byteBuffer.position(10); + * byteBuffer.put(stream); + *

+ * byteBuffer.position(10); + * assert object.equals(binarySerializer.deserializeFromByteBufferObject(buffer)) + * + *

+ * Final position of ByteBuffer will be changed and will be equal to + * sum of buffer start position and value returned by method {@link #getObjectSize(Object, Object...)} + * + * @param buffer Buffer which contains serialized presentation of object + * @return Instance of object serialized in buffer. + */ + T deserializeFromByteBufferObject(ByteBuffer buffer); + + /** + * Returns amount of bytes which is consumed by object which is already serialized in buffer. + *

+ * Position of buffer should be set before call of this method. + *

+ * Result of call should be the same as result of call of {@link #getObjectSize(Object, Object...)} on deserialized object. + * + * @param buffer Buffer which contains serialized version of object + * @return Size of serialized object. + */ + int getObjectSizeInByteBuffer(ByteBuffer buffer); + + /** + * Converts binary presentation of object to object instance taking in account changes which are done inside of atomic operation + * {@link com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation}. + *

+ * Binary format of method is expected to be the same as binary format of method {@link #serializeNativeObject(Object, byte[], int, Object...)}. + *

+ * So if we call: + * + * byte[] stream = new byte[serializedSize]; + * binarySerializer.serializeNativeObject(object, stream, 0); + * walChanges.setBinaryValue(buffer, stream, 10); + * + *

+ * Then following assert should pass + *

+ * + * assert object.equals(binarySerializer.deserializeFromByteBufferObject(buffer, walChanges, 10)); + * + * + * @param buffer Buffer which will contain serialized changes. + * @param walChanges Changes are done during atomic operation. + * @param offset Offset of binary presentation of object inside of byte buffer/atomic operations changes. + * @return Instance of object serialized in buffer. + */ + T deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset); + + /** + * Returns amount of bytes which is consumed by object which is already serialized in buffer taking in account + * changes which are done inside of atomic operation + * {@link com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation}. + *

+ * Result of call should be the same as result of call of {@link #getObjectSize(Object, Object...)} on deserialized object. + * + * @param buffer Buffer which will contain serialized changes. + * @param walChanges Changes are done during atomic operation. + * @param offset Offset of binary presentation of object inside of byte buffer/atomic operations changes. + * @return Size of serialized object. + */ + int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset); +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OBinaryTypeSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OBinaryTypeSerializer.java new file mode 100644 index 00000000000..6d98bc08968 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OBinaryTypeSerializer.java @@ -0,0 +1,145 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Serializer for byte arrays . + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 20.01.12 + */ +public class OBinaryTypeSerializer implements OBinarySerializer { + public static final OBinaryTypeSerializer INSTANCE = new OBinaryTypeSerializer(); + public static final byte ID = 17; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + + public int getObjectSize(int length) { + return length + OIntegerSerializer.INT_SIZE; + } + + public int getObjectSize(byte[] object, Object... hints) { + return object.length + OIntegerSerializer.INT_SIZE; + } + + public void serialize(final byte[] object, final byte[] stream, final int startPosition, final Object... hints) { + int len = object.length; + OIntegerSerializer.INSTANCE.serializeLiteral(len, stream, startPosition); + System.arraycopy(object, 0, stream, startPosition + OIntegerSerializer.INT_SIZE, len); + } + + public byte[] deserialize(final byte[] stream, final int startPosition) { + final int len = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + return Arrays + .copyOfRange(stream, startPosition + OIntegerSerializer.INT_SIZE, startPosition + OIntegerSerializer.INT_SIZE + len); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition) + OIntegerSerializer.INT_SIZE; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()) + OIntegerSerializer.INT_SIZE; + } + + public void serializeNativeObject(byte[] object, byte[] stream, int startPosition, Object... hints) { + final int len = object.length; + CONVERTER.putInt(stream, startPosition, len, ByteOrder.nativeOrder()); + System.arraycopy(object, 0, stream, startPosition + OIntegerSerializer.INT_SIZE, len); + } + + public byte[] deserializeNativeObject(byte[] stream, int startPosition) { + final int len = CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()); + return Arrays + .copyOfRange(stream, startPosition + OIntegerSerializer.INT_SIZE, startPosition + OIntegerSerializer.INT_SIZE + len); + } + + public byte getId() { + return ID; + } + + public boolean isFixedLength() { + return false; + } + + public int getFixedLength() { + return 0; + } + + @Override + public byte[] preprocess(byte[] value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(byte[] object, ByteBuffer buffer, Object... hints) { + final int len = object.length; + buffer.putInt(len); + buffer.put(object); + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] deserializeFromByteBufferObject(ByteBuffer buffer) { + final int len = buffer.getInt(); + final byte[] result = new byte[len]; + buffer.get(result); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return buffer.getInt() + OIntegerSerializer.INT_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final int len = walChanges.getIntValue(buffer, offset); + offset += OIntegerSerializer.INT_SIZE; + return walChanges.getBinaryValue(buffer, offset, len); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getIntValue(buffer, offset) + OIntegerSerializer.INT_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OBooleanSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OBooleanSerializer.java new file mode 100644 index 00000000000..0593fa7cb19 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OBooleanSerializer.java @@ -0,0 +1,143 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * Serializer for boolean type . + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OBooleanSerializer implements OBinarySerializer { + /** + * size of boolean value in bytes + */ + public static final int BOOLEAN_SIZE = 1; + public static final byte ID = 1; + public static final OBooleanSerializer INSTANCE = new OBooleanSerializer(); + + public int getObjectSize(Boolean object, Object... hints) { + return BOOLEAN_SIZE; + } + + public void serialize(final Boolean object, final byte[] stream, final int startPosition, final Object... hints) { + stream[startPosition] = object ? (byte) 1 : (byte) 0; + } + + public void serializeLiteral(final boolean value, final byte[] stream, final int startPosition) { + stream[startPosition] = value ? (byte) 1 : (byte) 0; + } + + public Boolean deserialize(final byte[] stream, final int startPosition) { + return stream[startPosition] == 1; + } + + public boolean deserializeLiteral(final byte[] stream, final int startPosition) { + return stream[startPosition] == 1; + } + + public int getObjectSize(byte[] stream, int startPosition) { + return BOOLEAN_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return BOOLEAN_SIZE; + } + + @Override + public void serializeNativeObject(final Boolean object, final byte[] stream, final int startPosition, final Object... hints) { + serialize(object, stream, startPosition); + } + + public void serializeNative(final boolean object, final byte[] stream, final int startPosition, final Object... hints) { + serializeLiteral(object, stream, startPosition); + } + + @Override + public Boolean deserializeNativeObject(final byte[] stream, final int startPosition) { + return deserialize(stream, startPosition); + } + + public boolean deserializeNative(final byte[] stream, final int startPosition) { + return deserializeLiteral(stream, startPosition); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return BOOLEAN_SIZE; + } + + @Override + public Boolean preprocess(final Boolean value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Boolean object, ByteBuffer buffer, Object... hints) { + buffer.put(object.booleanValue() ? (byte) 1 : (byte) 0); + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.get() > 0; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return BOOLEAN_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getByteValue(buffer, offset) > 0; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return BOOLEAN_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OByteSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OByteSerializer.java new file mode 100644 index 00000000000..5bf35db7b58 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OByteSerializer.java @@ -0,0 +1,143 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * Serializer for byte type . + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OByteSerializer implements OBinarySerializer { + /** + * size of byte value in bytes + */ + public static final int BYTE_SIZE = 1; + public static final byte ID = 2; + public static final OByteSerializer INSTANCE = new OByteSerializer(); + + public int getObjectSize(Byte object, Object... hints) { + return BYTE_SIZE; + } + + public void serialize(final Byte object, final byte[] stream, final int startPosition, final Object... hints) { + stream[startPosition] = object.byteValue(); + } + + public void serializeLiteral(final byte value, final byte[] stream, final int startPosition) { + stream[startPosition] = value; + } + + public Byte deserialize(final byte[] stream, final int startPosition) { + return stream[startPosition]; + } + + public byte deserializeLiteral(final byte[] stream, final int startPosition) { + return stream[startPosition]; + } + + public int getObjectSize(byte[] stream, int startPosition) { + return BYTE_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return getObjectSize(stream, startPosition); + } + + @Override + public void serializeNativeObject(final Byte object, final byte[] stream, final int startPosition, final Object... hints) { + serialize(object, stream, startPosition); + } + + public void serializeNative(byte object, byte[] stream, int startPosition, Object... hints) { + serializeLiteral(object, stream, startPosition); + } + + @Override + public Byte deserializeNativeObject(final byte[] stream, final int startPosition) { + return stream[startPosition]; + } + + public byte deserializeNative(final byte[] stream, final int startPosition) { + return stream[startPosition]; + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return BYTE_SIZE; + } + + @Override + public Byte preprocess(Byte value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Byte object, ByteBuffer buffer, Object... hints) { + buffer.put(object); + } + + /** + * {@inheritDoc} + */ + @Override + public Byte deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.get(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return BYTE_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Byte deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getByteValue(buffer, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return BYTE_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OCharSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OCharSerializer.java new file mode 100644 index 00000000000..cedb99cf645 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OCharSerializer.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OCharSerializer implements OBinarySerializer { + /** + * size of char value in bytes + */ + public static final int CHAR_SIZE = 2; + public static final byte ID = 3; + private static final OBinaryConverter BINARY_CONVERTER = OBinaryConverterFactory.getConverter(); + public static final OCharSerializer INSTANCE = new OCharSerializer(); + + public int getObjectSize(final Character object, Object... hints) { + return CHAR_SIZE; + } + + public void serialize(final Character object, final byte[] stream, final int startPosition, final Object... hints) { + serializeLiteral(object.charValue(), stream, startPosition); + } + + public void serializeLiteral(final char value, final byte[] stream, final int startPosition) { + stream[startPosition] = (byte) (value >>> 8); + stream[startPosition + 1] = (byte) (value); + } + + public Character deserialize(final byte[] stream, final int startPosition) { + return deserializeLiteral(stream, startPosition); + } + + public char deserializeLiteral(final byte[] stream, final int startPosition) { + return (char) (((stream[startPosition] & 0xFF) << 8) + (stream[startPosition + 1] & 0xFF)); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return CHAR_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return CHAR_SIZE; + } + + @Override + public void serializeNativeObject(Character object, byte[] stream, int startPosition, Object... hints) { + BINARY_CONVERTER.putChar(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + @Override + public Character deserializeNativeObject(final byte[] stream, final int startPosition) { + return BINARY_CONVERTER.getChar(stream, startPosition, ByteOrder.nativeOrder()); + } + + public void serializeNative(final char object, final byte[] stream, final int startPosition, final Object... hints) { + BINARY_CONVERTER.putChar(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + public char deserializeNative(final byte[] stream, final int startPosition) { + return BINARY_CONVERTER.getChar(stream, startPosition, ByteOrder.nativeOrder()); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return CHAR_SIZE; + } + + @Override + public Character preprocess(final Character value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Character object, ByteBuffer buffer, Object... hints) { + buffer.putChar(object); + } + + /** + * {@inheritDoc} + */ + @Override + public Character deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.getChar(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return CHAR_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Character deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return (char) walChanges.getShortValue(buffer, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return CHAR_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/ODateSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/ODateSerializer.java new file mode 100644 index 00000000000..c9525e23143 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/ODateSerializer.java @@ -0,0 +1,159 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.util.Calendar; +import java.util.Date; + +/** + * Serializer for {@link Date} type, it serializes it without time part. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 20.01.12 + */ +public class ODateSerializer implements OBinarySerializer { + + public static final byte ID = 4; + public static final ODateSerializer INSTANCE = new ODateSerializer(); + + public int getObjectSize(Date object, Object... hints) { + return OLongSerializer.LONG_SIZE; + } + + public void serialize(Date object, byte[] stream, int startPosition, Object... hints) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + dateTimeSerializer.serialize(calendar.getTime(), stream, startPosition); + } + + public Date deserialize(byte[] stream, int startPosition) { + ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + return dateTimeSerializer.deserialize(stream, startPosition); + } + + public int getObjectSize(byte[] stream, int startPosition) { + return OLongSerializer.LONG_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return OLongSerializer.LONG_SIZE; + } + + public void serializeNativeObject(final Date object, byte[] stream, int startPosition, Object... hints) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + dateTimeSerializer.serializeNativeObject(calendar.getTime(), stream, startPosition); + } + + public Date deserializeNativeObject(byte[] stream, int startPosition) { + ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + return dateTimeSerializer.deserializeNativeObject(stream, startPosition); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return OLongSerializer.LONG_SIZE; + } + + @Override + public Date preprocess(Date value, Object... hints) { + if(value==null){ + return null; + } + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(value); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + return calendar.getTime(); + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Date object, ByteBuffer buffer, Object... hints) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + dateTimeSerializer.serializeInByteBufferObject(calendar.getTime(), buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public Date deserializeFromByteBufferObject(ByteBuffer buffer) { + final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + return dateTimeSerializer.deserializeFromByteBufferObject(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return OLongSerializer.LONG_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Date deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE; + return dateTimeSerializer.deserializeFromByteBufferObject(buffer, walChanges, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OLongSerializer.LONG_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/ODateTimeSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/ODateTimeSerializer.java new file mode 100644 index 00000000000..33d55b5f361 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/ODateTimeSerializer.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.util.Calendar; +import java.util.Date; + +/** + * Serializer for {@link Date} type. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 20.01.12 + */ +public class ODateTimeSerializer implements OBinarySerializer { + public static final byte ID = 5; + public static final ODateTimeSerializer INSTANCE = new ODateTimeSerializer(); + + public int getObjectSize(Date object, Object... hints) { + return OLongSerializer.LONG_SIZE; + } + + public void serialize(Date object, byte[] stream, int startPosition, Object... hints) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + OLongSerializer.INSTANCE.serializeLiteral(calendar.getTimeInMillis(), stream, startPosition); + } + + public Date deserialize(byte[] stream, int startPosition) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(OLongSerializer.INSTANCE.deserializeLiteral(stream, startPosition)); + return calendar.getTime(); + } + + public int getObjectSize(byte[] stream, int startPosition) { + return OLongSerializer.LONG_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return OLongSerializer.LONG_SIZE; + } + + @Override + public void serializeNativeObject(Date object, byte[] stream, int startPosition, Object... hints) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + OLongSerializer.INSTANCE.serializeNative(calendar.getTimeInMillis(), stream, startPosition); + } + + @Override + public Date deserializeNativeObject(byte[] stream, int startPosition) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(OLongSerializer.INSTANCE.deserializeNative(stream, startPosition)); + return calendar.getTime(); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return OLongSerializer.LONG_SIZE; + } + + @Override + public Date preprocess(Date value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Date object, ByteBuffer buffer, Object... hints) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(object); + buffer.putLong(calendar.getTimeInMillis()); + } + + /** + * {@inheritDoc} + */ + @Override + public Date deserializeFromByteBufferObject(ByteBuffer buffer) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(buffer.getLong()); + return calendar.getTime(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return OLongSerializer.LONG_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Date deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(walChanges.getLongValue(buffer, offset)); + return calendar.getTime(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OLongSerializer.LONG_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/ODecimalSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/ODecimalSerializer.java new file mode 100644 index 00000000000..697feed66cd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/ODecimalSerializer.java @@ -0,0 +1,155 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; + +/** + * Serializer for {@link BigDecimal} type. + * + * @author Andrey Lomakin + * @since 03.04.12 + */ +public class ODecimalSerializer implements OBinarySerializer { + public static final ODecimalSerializer INSTANCE = new ODecimalSerializer(); + public static final byte ID = 18; + + public int getObjectSize(BigDecimal object, Object... hints) { + return OIntegerSerializer.INT_SIZE + OBinaryTypeSerializer.INSTANCE.getObjectSize(object.unscaledValue().toByteArray()); + } + + public int getObjectSize(byte[] stream, int startPosition) { + final int size = OIntegerSerializer.INT_SIZE + OBinaryTypeSerializer.INSTANCE + .getObjectSize(stream, startPosition + OIntegerSerializer.INT_SIZE); + return size; + } + + public void serialize(BigDecimal object, byte[] stream, int startPosition, Object... hints) { + OIntegerSerializer.INSTANCE.serializeLiteral(object.scale(), stream, startPosition); + startPosition += OIntegerSerializer.INT_SIZE; + OBinaryTypeSerializer.INSTANCE.serialize(object.unscaledValue().toByteArray(), stream, startPosition); + + } + + public BigDecimal deserialize(final byte[] stream, int startPosition) { + final int scale = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + startPosition += OIntegerSerializer.INT_SIZE; + + final byte[] unscaledValue = OBinaryTypeSerializer.INSTANCE.deserialize(stream, startPosition); + + return new BigDecimal(new BigInteger(unscaledValue), scale); + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + final int size = OIntegerSerializer.INT_SIZE + OBinaryTypeSerializer.INSTANCE + .getObjectSizeNative(stream, startPosition + OIntegerSerializer.INT_SIZE); + return size; + } + + @Override + public void serializeNativeObject(BigDecimal object, byte[] stream, int startPosition, Object... hints) { + OIntegerSerializer.INSTANCE.serializeNative(object.scale(), stream, startPosition); + startPosition += OIntegerSerializer.INT_SIZE; + OBinaryTypeSerializer.INSTANCE.serializeNativeObject(object.unscaledValue().toByteArray(), stream, startPosition); + } + + @Override + public BigDecimal deserializeNativeObject(byte[] stream, int startPosition) { + final int scale = OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition); + startPosition += OIntegerSerializer.INT_SIZE; + + final byte[] unscaledValue = OBinaryTypeSerializer.INSTANCE.deserializeNativeObject(stream, startPosition); + + return new BigDecimal(new BigInteger(unscaledValue), scale); + } + + public boolean isFixedLength() { + return false; + } + + public int getFixedLength() { + return 0; + } + + @Override + public BigDecimal preprocess(BigDecimal value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(BigDecimal object, ByteBuffer buffer, Object... hints) { + buffer.putInt(object.scale()); + OBinaryTypeSerializer.INSTANCE.serializeInByteBufferObject(object.unscaledValue().toByteArray(), buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public BigDecimal deserializeFromByteBufferObject(ByteBuffer buffer) { + final int scale = buffer.getInt(); + final byte[] unscaledValue = OBinaryTypeSerializer.INSTANCE.deserializeFromByteBufferObject(buffer); + + return new BigDecimal(new BigInteger(unscaledValue), scale); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + buffer.position(buffer.position() + OIntegerSerializer.INT_SIZE); + return OIntegerSerializer.INT_SIZE + OBinaryTypeSerializer.INSTANCE.getObjectSizeInByteBuffer(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public BigDecimal deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final int scale = walChanges.getIntValue(buffer, offset); + offset += OIntegerSerializer.INT_SIZE; + + final byte[] unscaledValue = OBinaryTypeSerializer.INSTANCE.deserializeFromByteBufferObject(buffer, walChanges, offset); + + return new BigDecimal(new BigInteger(unscaledValue), scale); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OIntegerSerializer.INT_SIZE + OBinaryTypeSerializer.INSTANCE + .getObjectSizeInByteBuffer(buffer, walChanges, offset + OIntegerSerializer.INT_SIZE); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/ODoubleSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/ODoubleSerializer.java new file mode 100644 index 00000000000..ed413f5a215 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/ODoubleSerializer.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Serializer for {@link Double} + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 17.01.12 + */ +public class ODoubleSerializer implements OBinarySerializer { + public static final byte ID = 6; + /** + * size of double value in bytes + */ + public static final int DOUBLE_SIZE = 8; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final ODoubleSerializer INSTANCE = new ODoubleSerializer(); + + public int getObjectSize(Double object, Object... hints) { + return DOUBLE_SIZE; + } + + public void serialize(final Double object, final byte[] stream, final int startPosition, final Object... hints) { + OLongSerializer.INSTANCE.serializeLiteral(Double.doubleToLongBits(object), stream, startPosition); + } + + public Double deserialize(final byte[] stream, final int startPosition) { + return Double.longBitsToDouble(OLongSerializer.INSTANCE.deserializeLiteral(stream, startPosition)); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return DOUBLE_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + return DOUBLE_SIZE; + } + + public void serializeNative(final double object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putLong(stream, startPosition, Double.doubleToLongBits(object), ByteOrder.nativeOrder()); + } + + public double deserializeNative(byte[] stream, int startPosition) { + return Double.longBitsToDouble(CONVERTER.getLong(stream, startPosition, ByteOrder.nativeOrder())); + } + + @Override + public void serializeNativeObject(final Double object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putLong(stream, startPosition, Double.doubleToLongBits(object), ByteOrder.nativeOrder()); + } + + @Override + public Double deserializeNativeObject(byte[] stream, int startPosition) { + return Double.longBitsToDouble(CONVERTER.getLong(stream, startPosition, ByteOrder.nativeOrder())); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return DOUBLE_SIZE; + } + + @Override + public Double preprocess(final Double value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Double object, ByteBuffer buffer, Object... hints) { + buffer.putLong(Double.doubleToLongBits(object)); + } + + /** + * {@inheritDoc} + */ + @Override + public Double deserializeFromByteBufferObject(ByteBuffer buffer) { + return Double.longBitsToDouble(buffer.getLong()); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return DOUBLE_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Double deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return Double.longBitsToDouble(walChanges.getLongValue(buffer, offset)); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return DOUBLE_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OFloatSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OFloatSerializer.java new file mode 100644 index 00000000000..606c50e324a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OFloatSerializer.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Serializer for {@link Float} type. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OFloatSerializer implements OBinarySerializer { + public static final byte ID = 7; + /** + * size of float value in bytes + */ + public static final int FLOAT_SIZE = 4; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final OFloatSerializer INSTANCE = new OFloatSerializer(); + + public int getObjectSize(Float object, Object... hints) { + return FLOAT_SIZE; + } + + public void serialize(Float object, byte[] stream, int startPosition, Object... hints) { + OIntegerSerializer.INSTANCE.serializeLiteral(Float.floatToIntBits(object), stream, startPosition); + } + + public Float deserialize(final byte[] stream, final int startPosition) { + return Float.intBitsToFloat(OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition)); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return FLOAT_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return FLOAT_SIZE; + } + + @Override + public void serializeNativeObject(Float object, byte[] stream, int startPosition, Object... hints) { + CONVERTER.putInt(stream, startPosition, Float.floatToIntBits(object), ByteOrder.nativeOrder()); + } + + @Override + public Float deserializeNativeObject(byte[] stream, int startPosition) { + return Float.intBitsToFloat(CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder())); + } + + public void serializeNative(final float object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putInt(stream, startPosition, Float.floatToIntBits(object), ByteOrder.nativeOrder()); + } + + public float deserializeNative(final byte[] stream, final int startPosition) { + return Float.intBitsToFloat(CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder())); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return FLOAT_SIZE; + } + + @Override + public Float preprocess(final Float value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Float object, ByteBuffer buffer, Object... hints) { + buffer.putInt(Float.floatToIntBits(object)); + } + + /** + * {@inheritDoc} + */ + @Override + public Float deserializeFromByteBufferObject(ByteBuffer buffer) { + return Float.intBitsToFloat(buffer.getInt()); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return FLOAT_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Float deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return Float.intBitsToFloat(walChanges.getIntValue(buffer, offset)); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return FLOAT_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OIntegerSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OIntegerSerializer.java new file mode 100644 index 00000000000..6140476059a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OIntegerSerializer.java @@ -0,0 +1,151 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Serializer for {@link Integer} type. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 17.01.12 + */ +public class OIntegerSerializer implements OBinarySerializer { + public static final byte ID = 8; + /** + * size of int value in bytes + */ + public static final int INT_SIZE = 4; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final OIntegerSerializer INSTANCE = new OIntegerSerializer(); + + public int getObjectSize(Integer object, Object... hints) { + return INT_SIZE; + } + + public void serialize(final Integer object, final byte[] stream, final int startPosition, final Object... hints) { + serializeLiteral(object.intValue(), stream, startPosition); + } + + public void serializeLiteral(final int value, final byte[] stream, final int startPosition) { + stream[startPosition] = (byte) ((value >>> 24) & 0xFF); + stream[startPosition + 1] = (byte) ((value >>> 16) & 0xFF); + stream[startPosition + 2] = (byte) ((value >>> 8) & 0xFF); + stream[startPosition + 3] = (byte) ((value >>> 0) & 0xFF); + } + + public Integer deserialize(final byte[] stream, final int startPosition) { + return deserializeLiteral(stream, startPosition); + } + + public int deserializeLiteral(final byte[] stream, final int startPosition) { + return (stream[startPosition]) << 24 | (0xff & stream[startPosition + 1]) << 16 | (0xff & stream[startPosition + 2]) << 8 | (( + 0xff & stream[startPosition + 3])); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return INT_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + return INT_SIZE; + } + + @Override + public void serializeNativeObject(Integer object, byte[] stream, int startPosition, Object... hints) { + CONVERTER.putInt(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + @Override + public Integer deserializeNativeObject(final byte[] stream, final int startPosition) { + return CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()); + } + + public void serializeNative(int object, byte[] stream, int startPosition, Object... hints) { + CONVERTER.putInt(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + public int deserializeNative(final byte[] stream, final int startPosition) { + return CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return INT_SIZE; + } + + @Override + public Integer preprocess(final Integer value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Integer object, ByteBuffer buffer, Object... hints) { + buffer.putInt(object); + } + + /** + * {@inheritDoc} + */ + @Override + public Integer deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.getInt(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return INT_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Integer deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getIntValue(buffer, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return INT_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OLongSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OLongSerializer.java new file mode 100644 index 00000000000..e32fe9d4fd0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OLongSerializer.java @@ -0,0 +1,157 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Serializer for {@link Long} type. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OLongSerializer implements OBinarySerializer { + public static final byte ID = 10; + /** + * size of long value in bytes + */ + public static final int LONG_SIZE = 8; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final OLongSerializer INSTANCE = new OLongSerializer(); + + public int getObjectSize(final Long object, final Object... hints) { + return LONG_SIZE; + } + + public void serialize(final Long object, final byte[] stream, final int startPosition, final Object... hints) { + serializeLiteral(object.longValue(), stream, startPosition); + } + + public void serializeLiteral(final long value, final byte[] stream, final int startPosition) { + stream[startPosition] = (byte) ((value >>> 56) & 0xFF); + stream[startPosition + 1] = (byte) ((value >>> 48) & 0xFF); + stream[startPosition + 2] = (byte) ((value >>> 40) & 0xFF); + stream[startPosition + 3] = (byte) ((value >>> 32) & 0xFF); + stream[startPosition + 4] = (byte) ((value >>> 24) & 0xFF); + stream[startPosition + 5] = (byte) ((value >>> 16) & 0xFF); + stream[startPosition + 6] = (byte) ((value >>> 8) & 0xFF); + stream[startPosition + 7] = (byte) ((value >>> 0) & 0xFF); + } + + public Long deserialize(final byte[] stream, final int startPosition) { + return deserializeLiteral(stream, startPosition); + } + + public long deserializeLiteral(final byte[] stream, final int startPosition) { + return ((0xff & stream[startPosition + 7]) | (0xff & stream[startPosition + 6]) << 8 | (0xff & stream[startPosition + 5]) << 16 + | (long) (0xff & stream[startPosition + 4]) << 24 | (long) (0xff & stream[startPosition + 3]) << 32 + | (long) (0xff & stream[startPosition + 2]) << 40 | (long) (0xff & stream[startPosition + 1]) << 48 + | (long) (0xff & stream[startPosition]) << 56); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return LONG_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + return LONG_SIZE; + } + + @Override + public void serializeNativeObject(final Long object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putLong(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + @Override + public Long deserializeNativeObject(final byte[] stream, final int startPosition) { + return CONVERTER.getLong(stream, startPosition, ByteOrder.nativeOrder()); + } + + public void serializeNative(final long object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putLong(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + public long deserializeNative(final byte[] stream, final int startPosition) { + return CONVERTER.getLong(stream, startPosition, ByteOrder.nativeOrder()); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return LONG_SIZE; + } + + @Override + public Long preprocess(final Long value, final Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Long object, ByteBuffer buffer, Object... hints) { + buffer.putLong(object); + } + + /** + * {@inheritDoc} + */ + @Override + public Long deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.getLong(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return LONG_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Long deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getLongValue(buffer, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return LONG_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/ONullSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/ONullSerializer.java new file mode 100644 index 00000000000..42a62f268e8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/ONullSerializer.java @@ -0,0 +1,120 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * Serializes and deserializes null values. + * + * @author Evgeniy Degtiarenko (gmandnepr-at-gmail.com) + */ +public class ONullSerializer implements OBinarySerializer { + + public static final byte ID = 11; + public static final ONullSerializer INSTANCE = new ONullSerializer(); + + public int getObjectSize(final Object object, Object... hints) { + return 0; + } + + public void serialize(final Object object, final byte[] stream, final int startPosition, Object... hints) { + // nothing to serialize + } + + public Object deserialize(final byte[] stream, final int startPosition) { + // nothing to deserialize + return null; + } + + public int getObjectSize(byte[] stream, int startPosition) { + return 0; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return 0; + } + + public void serializeNativeObject(Object object, byte[] stream, int startPosition, Object... hints) { + } + + public Object deserializeNativeObject(byte[] stream, int startPosition) { + return null; + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return 0; + } + + @Override + public Object preprocess(Object value, Object... hints) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Object object, ByteBuffer buffer, Object... hints) { + } + + /** + * {@inheritDoc} + */ + @Override + public Object deserializeFromByteBufferObject(ByteBuffer buffer) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public Object deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return 0; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OSerializationHelper.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OSerializationHelper.java new file mode 100644 index 00000000000..654e2a999ba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OSerializationHelper.java @@ -0,0 +1,65 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import java.util.Map; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSerializationHelper { + public static final OSerializationHelper INSTANCE = new OSerializationHelper(); + + public byte[] serialize(Map map, OBinarySerializer keySerializer, OBinarySerializer valueSerializer) { + final int size = length(map, keySerializer, valueSerializer); + final byte[] stream = new byte[size]; + + serialize(map, stream, 0, keySerializer, valueSerializer); + + return stream; + } + + public int length(Map map, OBinarySerializer keySerializer, OBinarySerializer valueSerializer) { + final int mapSize = map.size(); + int length = OIntegerSerializer.INT_SIZE; + assert keySerializer.isFixedLength(); + length += mapSize * keySerializer.getFixedLength(); + + assert valueSerializer.isFixedLength(); + length += mapSize * valueSerializer.getFixedLength(); + + return length; + } + + private void serialize(Map map, byte[] stream, int offset, OBinarySerializer keySerializer, + OBinarySerializer valueSerializer) { + OIntegerSerializer.INSTANCE.serializeLiteral(map.size(), stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + for (Map.Entry entry : map.entrySet()) { + keySerializer.serialize(entry.getKey(), stream, offset); + offset += keySerializer.getObjectSize(entry.getKey()); + + valueSerializer.serialize(entry.getValue(), stream, offset); + offset += valueSerializer.getObjectSize(entry.getValue()); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OShortSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OShortSerializer.java new file mode 100644 index 00000000000..5f14c6a8968 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OShortSerializer.java @@ -0,0 +1,148 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Serializer for {@link Short}. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OShortSerializer implements OBinarySerializer { + public static final byte ID = 12; + /** + * size of short value in bytes + */ + public static final int SHORT_SIZE = 2; + private static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final OShortSerializer INSTANCE = new OShortSerializer(); + + public int getObjectSize(Short object, Object... hints) { + return SHORT_SIZE; + } + + public void serialize(final Short object, final byte[] stream, final int startPosition, final Object... hints) { + serializeLiteral(object.shortValue(), stream, startPosition); + } + + public void serializeLiteral(final short value, final byte[] stream, final int startPosition) { + stream[startPosition] = (byte) ((value >>> 8) & 0xFF); + stream[startPosition + 1] = (byte) ((value >>> 0) & 0xFF); + } + + public Short deserialize(final byte[] stream, final int startPosition) { + return deserializeLiteral(stream, startPosition); + } + + public short deserializeLiteral(final byte[] stream, final int startPosition) { + return (short) ((stream[startPosition] << 8) | (stream[startPosition + 1] & 0xff)); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return SHORT_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + return SHORT_SIZE; + } + + @Override + public void serializeNativeObject(final Short object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putShort(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + @Override + public Short deserializeNativeObject(byte[] stream, int startPosition) { + return CONVERTER.getShort(stream, startPosition, ByteOrder.nativeOrder()); + } + + public void serializeNative(final short object, final byte[] stream, final int startPosition, final Object... hints) { + CONVERTER.putShort(stream, startPosition, object, ByteOrder.nativeOrder()); + } + + public short deserializeNative(byte[] stream, int startPosition) { + return CONVERTER.getShort(stream, startPosition, ByteOrder.nativeOrder()); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return SHORT_SIZE; + } + + @Override + public Short preprocess(Short value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(Short object, ByteBuffer buffer, Object... hints) { + buffer.putShort(object); + } + + /** + * {@inheritDoc} + */ + @Override + public Short deserializeFromByteBufferObject(ByteBuffer buffer) { + return buffer.getShort(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return SHORT_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public Short deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getShortValue(buffer, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return SHORT_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OStringSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OStringSerializer.java new file mode 100644 index 00000000000..0af711bf71c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OStringSerializer.java @@ -0,0 +1,206 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * Serializer for {@link String} type. + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 18.01.12 + */ +public class OStringSerializer implements OBinarySerializer { + public static final OStringSerializer INSTANCE = new OStringSerializer(); + public static final byte ID = 13; + + public int getObjectSize(final String object, Object... hints) { + return object.length() * 2 + OIntegerSerializer.INT_SIZE; + } + + public void serialize(final String object, final byte[] stream, int startPosition, Object... hints) { + final int length = object.length(); + OIntegerSerializer.INSTANCE.serializeLiteral(length, stream, startPosition); + + startPosition += OIntegerSerializer.INT_SIZE; + final char[] stringContent = new char[length]; + + object.getChars(0, length, stringContent, 0); + + for (char character : stringContent) { + stream[startPosition] = (byte) character; + startPosition++; + + stream[startPosition] = (byte) (character >>> 8); + startPosition++; + } + } + + public String deserialize(final byte[] stream, int startPosition) { + final int len = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + final char[] buffer = new char[len]; + + startPosition += OIntegerSerializer.INT_SIZE; + + for (int i = 0; i < len; i++) { + buffer[i] = (char) ((0xFF & stream[startPosition]) | ((0xFF & stream[startPosition + 1]) << 8)); + startPosition += 2; + } + + return new String(buffer); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition) * 2 + OIntegerSerializer.INT_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition) * 2 + OIntegerSerializer.INT_SIZE; + } + + @Override + public void serializeNativeObject(String object, byte[] stream, int startPosition, Object... hints) { + int length = object.length(); + OIntegerSerializer.INSTANCE.serializeNative(length, stream, startPosition); + + startPosition += OIntegerSerializer.INT_SIZE; + char[] stringContent = new char[length]; + + object.getChars(0, length, stringContent, 0); + + for (char character : stringContent) { + stream[startPosition] = (byte) character; + startPosition++; + + stream[startPosition] = (byte) (character >>> 8); + startPosition++; + } + } + + public String deserializeNativeObject(byte[] stream, int startPosition) { + int len = OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition); + char[] buffer = new char[len]; + + startPosition += OIntegerSerializer.INT_SIZE; + + for (int i = 0; i < len; i++) { + buffer[i] = (char) ((0xFF & stream[startPosition]) | ((0xFF & stream[startPosition + 1]) << 8)); + startPosition += 2; + } + + return new String(buffer); + } + + public boolean isFixedLength() { + return false; + } + + public int getFixedLength() { + throw new UnsupportedOperationException("Length of serialized string is not fixed."); + } + + @Override + public String preprocess(String value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(String object, ByteBuffer buffer, Object... hints) { + int length = object.length(); + buffer.putInt(length); + + byte[] binaryData = new byte[length * 2]; + char[] stringContent = new char[length]; + + object.getChars(0, length, stringContent, 0); + + int counter = 0; + for (char character : stringContent) { + binaryData[counter] = (byte) character; + counter++; + + binaryData[counter] = (byte) (character >>> 8); + counter++; + } + + buffer.put(binaryData); + } + + /** + * {@inheritDoc} + */ + @Override + public String deserializeFromByteBufferObject(ByteBuffer buffer) { + int len = buffer.getInt(); + + final char[] chars = new char[len]; + final byte[] binaryData = new byte[2 * len]; + buffer.get(binaryData); + + for (int i = 0; i < len; i++) + chars[i] = (char) ((0xFF & binaryData[i << 1]) | ((0xFF & binaryData[(i << 1) + 1]) << 8)); + + return new String(chars); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return buffer.getInt() * 2 + OIntegerSerializer.INT_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public String deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + int len = walChanges.getIntValue(buffer, offset); + + final char[] chars = new char[len]; + offset += OIntegerSerializer.INT_SIZE; + + byte[] binaryData = walChanges.getBinaryValue(buffer, offset, 2 * len); + + for (int i = 0; i < len; i++) + chars[i] = (char) ((0xFF & binaryData[i << 1]) | ((0xFF & binaryData[(i << 1) + 1]) << 8)); + + return new String(chars); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getIntValue(buffer, offset) * 2 + OIntegerSerializer.INT_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/serialization/types/OUUIDSerializer.java b/core/src/main/java/com/orientechnologies/common/serialization/types/OUUIDSerializer.java new file mode 100644 index 00000000000..0123355ae41 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/serialization/types/OUUIDSerializer.java @@ -0,0 +1,142 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.serialization.types; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OUUIDSerializer implements OBinarySerializer { + public static final OUUIDSerializer INSTANCE = new OUUIDSerializer(); + public static final int UUID_SIZE = 2 * OLongSerializer.LONG_SIZE; + + @Override + public int getObjectSize(UUID object, Object... hints) { + return UUID_SIZE; + } + + @Override + public int getObjectSize(byte[] stream, int startPosition) { + return UUID_SIZE; + } + + @Override + public void serialize(final UUID object, final byte[] stream, final int startPosition, final Object... hints) { + OLongSerializer.INSTANCE.serializeLiteral(object.getMostSignificantBits(), stream, startPosition); + OLongSerializer.INSTANCE.serializeLiteral(object.getLeastSignificantBits(), stream, startPosition + OLongSerializer.LONG_SIZE); + } + + @Override + public UUID deserialize(byte[] stream, int startPosition) { + final long mostSignificantBits = OLongSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + final long leastSignificantBits = OLongSerializer.INSTANCE + .deserializeLiteral(stream, startPosition + OLongSerializer.LONG_SIZE); + return new UUID(mostSignificantBits, leastSignificantBits); + } + + @Override + public byte getId() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFixedLength() { + return OLongSerializer.INSTANCE.isFixedLength(); + } + + @Override + public int getFixedLength() { + return UUID_SIZE; + } + + @Override + public void serializeNativeObject(final UUID object, final byte[] stream, final int startPosition, final Object... hints) { + OLongSerializer.INSTANCE.serializeNative(object.getMostSignificantBits(), stream, startPosition, hints); + OLongSerializer.INSTANCE + .serializeNative(object.getLeastSignificantBits(), stream, startPosition + OLongSerializer.LONG_SIZE, hints); + } + + @Override + public UUID deserializeNativeObject(final byte[] stream, final int startPosition) { + final long mostSignificantBits = OLongSerializer.INSTANCE.deserializeNative(stream, startPosition); + final long leastSignificantBits = OLongSerializer.INSTANCE.deserializeNative(stream, startPosition + OLongSerializer.LONG_SIZE); + return new UUID(mostSignificantBits, leastSignificantBits); + } + + @Override + public int getObjectSizeNative(final byte[] stream, final int startPosition) { + return UUID_SIZE; + } + + @Override + public UUID preprocess(UUID value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(UUID object, ByteBuffer buffer, Object... hints) { + buffer.putLong(object.getMostSignificantBits()); + buffer.putLong(object.getLeastSignificantBits()); + } + + /** + * {@inheritDoc} + */ + @Override + public UUID deserializeFromByteBufferObject(ByteBuffer buffer) { + final long mostSignificantBits = buffer.getLong(); + final long leastSignificantBits = buffer.getLong(); + return new UUID(mostSignificantBits, leastSignificantBits); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return UUID_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public UUID deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final long mostSignificantBits = walChanges.getLongValue(buffer, offset); + final long leastSignificantBits = walChanges.getLongValue(buffer, offset + OLongSerializer.LONG_SIZE); + return new UUID(mostSignificantBits, leastSignificantBits); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return UUID_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/thread/OPollerThread.java b/core/src/main/java/com/orientechnologies/common/thread/OPollerThread.java new file mode 100644 index 00000000000..4e0d59805c8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/thread/OPollerThread.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.thread; + +public abstract class OPollerThread extends OSoftThread { + protected final long delay; + + public OPollerThread(final long iDelay) { + delay = iDelay; + } + + public OPollerThread(long iDelay, final ThreadGroup iThreadGroup) { + super(iThreadGroup, OPollerThread.class.getSimpleName()); + delay = iDelay; + } + + public OPollerThread(final long iDelay, final String name) { + super(name); + delay = iDelay; + } + + public OPollerThread(final long iDelay, final ThreadGroup group, final String name) { + super(group, name); + delay = iDelay; + } + + @Override + protected void afterExecution() throws InterruptedException { + pauseCurrentThread(delay); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/thread/OSoftThread.java b/core/src/main/java/com/orientechnologies/common/thread/OSoftThread.java new file mode 100755 index 00000000000..1b3eb300982 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/thread/OSoftThread.java @@ -0,0 +1,122 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.thread; + +import com.orientechnologies.common.util.OService; + +public abstract class OSoftThread extends Thread implements OService { + private volatile boolean shutdownFlag; + + private boolean dumpExceptions = true; + + public OSoftThread() { + } + + public OSoftThread(final ThreadGroup iThreadGroup) { + super(iThreadGroup, OSoftThread.class.getSimpleName()); + setDaemon(true); + } + + public OSoftThread(final String name) { + super(name); + setDaemon(true); + } + + public OSoftThread(final ThreadGroup group, final String name) { + super(group, name); + setDaemon(true); + } + + + protected abstract void execute() throws Exception; + + public void startup() { + } + + public void shutdown() { + } + + public void sendShutdown() { + shutdownFlag = true; + interrupt(); + } + + public void softShutdown() { + shutdownFlag = true; + } + + public boolean isShutdownFlag() { + return shutdownFlag; + } + + @Override + public void run() { + startup(); + + while (!shutdownFlag && !isInterrupted()) { + try { + beforeExecution(); + execute(); + afterExecution(); + } catch (Throwable t) { + if (dumpExceptions) + t.printStackTrace(); + } + } + + shutdown(); + } + + /** + * Pauses current thread until iTime timeout or a wake up by another thread. + * + * @param iTime + * @return true if timeout has reached, otherwise false. False is the case of wake-up by another thread. + */ + public static boolean pauseCurrentThread(long iTime) { + try { + if (iTime<=0) + iTime = Long.MAX_VALUE; + + Thread.sleep(iTime); + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + public boolean isDumpExceptions() { + return dumpExceptions; + } + + public void setDumpExceptions(final boolean dumpExceptions) { + this.dumpExceptions = dumpExceptions; + } + + protected void beforeExecution() throws InterruptedException { + return; + } + + protected void afterExecution() throws InterruptedException { + return; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/types/OBinary.java b/core/src/main/java/com/orientechnologies/common/types/OBinary.java new file mode 100644 index 00000000000..ea076fb80b1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/types/OBinary.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.types; + +/** + * Binary wrapper to let to byte[] to be managed inside OrientDB where comparable is needed, like for indexes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * Deprecated sice v2.2 + */ +@Deprecated +public class OBinary implements Comparable { + private final byte[] value; + + public OBinary(final byte[] buffer) { + value = buffer; + } + + public int compareTo(final OBinary o) { + final int size = value.length; + + for (int i = 0; i < size; ++i) { + if (value[i] > o.value[i]) + return 1; + else if (value[i] < o.value[i]) + return -1; + } + return 0; + } + + public byte[] toByteArray() { + return value; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/types/OModifiableBoolean.java b/core/src/main/java/com/orientechnologies/common/types/OModifiableBoolean.java new file mode 100644 index 00000000000..d9a33189e66 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/types/OModifiableBoolean.java @@ -0,0 +1,27 @@ +package com.orientechnologies.common.types; + +/** + * This internal API please do not use it. + * + * @author Andrey Lomakin Andrey Lomakin + * @since 19/12/14 + */ +public class OModifiableBoolean { + private boolean value; + + public OModifiableBoolean() { + value = false; + } + + public OModifiableBoolean(boolean value) { + this.value = value; + } + + public boolean getValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/types/OModifiableInteger.java b/core/src/main/java/com/orientechnologies/common/types/OModifiableInteger.java new file mode 100644 index 00000000000..835b939c0e5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/types/OModifiableInteger.java @@ -0,0 +1,122 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.types; + +/** + * Modifiable Integer. Like java.lang.Integer but the value is modifiable. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("serial") +public class OModifiableInteger extends Number implements Comparable { + public int value; + + public OModifiableInteger() { + value = 0; + } + + public OModifiableInteger(final int iValue) { + value = iValue; + } + + public void setValue(final int iValue) { + value = iValue; + } + + public int getValue() { + return value; + } + + public void increment() { + value++; + } + + public void increment(final int iValue) { + value += iValue; + } + + public void decrement() { + value--; + } + + public void decrement(final int iValue) { + value -= iValue; + } + + public int compareTo(final OModifiableInteger anotherInteger) { + int thisVal = value; + int anotherVal = anotherInteger.value; + + return (thisVal < anotherVal) ? -1 : ((thisVal == anotherVal) ? 0 : 1); + } + + @Override + public byte byteValue() { + return (byte) value; + } + + @Override + public short shortValue() { + return (short) value; + } + + @Override + public float floatValue() { + return value; + } + + @Override + public double doubleValue() { + return value; + } + + @Override + public int intValue() { + return value; + } + + @Override + public long longValue() { + return value; + } + + public Integer toInteger() { + return Integer.valueOf(this.value); + } + + @Override + public boolean equals(final Object o) { + if (o instanceof OModifiableInteger) { + return value == ((OModifiableInteger) o).value; + } + return false; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/types/OModifiableLong.java b/core/src/main/java/com/orientechnologies/common/types/OModifiableLong.java new file mode 100755 index 00000000000..b9c9582b671 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/types/OModifiableLong.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.types; + +public class OModifiableLong extends Number implements Comparable { + public long value; + + public OModifiableLong() { + value = 0; + } + + public OModifiableLong(final long iValue) { + value = iValue; + } + + public void setValue(final long iValue) { + value = iValue; + } + + public long getValue() { + return value; + } + + public void increment() { + value++; + } + + public void increment(final long iValue) { + value += iValue; + } + + public void decrement() { + value--; + } + + public void decrement(final long iValue) { + value -= iValue; + } + + public int compareTo(final OModifiableLong anotherInteger) { + long thisVal = value; + long anotherVal = anotherInteger.value; + + return (thisVal < anotherVal) ? -1 : ((thisVal == anotherVal) ? 0 : 1); + } + + @Override + public byte byteValue() { + return (byte) value; + } + + @Override + public short shortValue() { + return (short) value; + } + + @Override + public float floatValue() { + return value; + } + + @Override + public double doubleValue() { + return value; + } + + @Override + public int intValue() { + return (int) value; + } + + @Override + public long longValue() { + return value; + } + + public Long toLong() { + return Long.valueOf(this.value); + } + + @Override + public boolean equals(final Object o) { + if (o instanceof OModifiableLong) { + return value == ((OModifiableLong) o).value; + } + return false; + } + + @Override + public int hashCode() { + return Long.valueOf(value).hashCode(); + } + + @Override + public String toString() { + return String.valueOf(this.value); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/types/ORef.java b/core/src/main/java/com/orientechnologies/common/types/ORef.java new file mode 100644 index 00000000000..96576b821a8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/types/ORef.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.types; + +/** + * Modifiable value. Point to the real value. Useful to pass in method as parameter, letting the method to change. A sort of void* + * of C language. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORef { + public T value; + + public ORef() { + } + + public ORef(final T object) { + this.value = object; + } + + public ORef clear() { + value = null; + return this; + } + + @Override + public String toString() { + return value != null ? value.toString() : "ORef"; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/HeapDumper.java b/core/src/main/java/com/orientechnologies/common/util/HeapDumper.java new file mode 100755 index 00000000000..36a8d497b3a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/HeapDumper.java @@ -0,0 +1,29 @@ +package com.orientechnologies.common.util; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import java.lang.management.ManagementFactory; + +public class HeapDumper { + // This is the name of the HotSpot Diagnostic MBean + private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; + + /** + * Invoke {@code dumpHeap} operation on {@code com.sun.management:type=HotSpotDiagnostic} mbean. + */ + public static void dumpHeap(String fileName, boolean live) { + try { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + server.invoke(new ObjectName(HOTSPOT_BEAN_NAME), + "dumpHeap", + new Object[]{fileName, live}, + new String[]{String.class.getName(), Boolean.TYPE.getName()} + ); + } catch (RuntimeException re) { + throw re; + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OApi.java b/core/src/main/java/com/orientechnologies/common/util/OApi.java new file mode 100644 index 00000000000..5727f129feb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OApi.java @@ -0,0 +1,27 @@ +package com.orientechnologies.common.util; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation to mark OrientDB API. Maturity has the following meaning: + *
    + *
  • EXPERIMENTAL: the API can change or could be completely dropped
  • + *
  • NEW: the API is new and could be immature
  • + *
  • STABLE: the API is stable and used by users. Any change to the API pass for @Deprecated and official announcements
  • + *
  • DEPRECATED: the API has been deprecated. Usually a better alternative is provided in JavaDoc. The Deprecated API could be + * removed on further releases
  • + *
+ * + * @author Luca Garulli + */ +@Retention(RetentionPolicy.SOURCE) +public @interface OApi { + enum MATURITY { + EXPERIMENTAL, NEW, STABLE, DEPRECATED + } + + boolean enduser() default true; + + MATURITY maturity() default MATURITY.NEW; +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OArrays.java b/core/src/main/java/com/orientechnologies/common/util/OArrays.java new file mode 100644 index 00000000000..b616ad2eebe --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OArrays.java @@ -0,0 +1,112 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +import java.lang.reflect.Array; + +import com.orientechnologies.common.log.OLogManager; + +@SuppressWarnings("unchecked") +public class OArrays { + public static T[] copyOf(final T[] iSource, final int iNewSize) { + return (T[]) copyOf(iSource, iNewSize, iSource.getClass()); + } + + public static T[] copyOf(final U[] iSource, final int iNewSize, final Class iNewType) { + final T[] copy = ((Object) iNewType == (Object) Object[].class) ? (T[]) new Object[iNewSize] : (T[]) Array.newInstance( + iNewType.getComponentType(), iNewSize); + System.arraycopy(iSource, 0, copy, 0, Math.min(iSource.length, iNewSize)); + return copy; + } + + public static S[] copyOfRange(final S[] iSource, final int iBegin, final int iEnd) { + return copyOfRange(iSource, iBegin, iEnd, (Class) iSource.getClass()); + } + + public static D[] copyOfRange(final S[] iSource, final int iBegin, final int iEnd, final Class iClass) { + final int newLength = iEnd - iBegin; + if (newLength < 0) + throw new IllegalArgumentException(iBegin + " > " + iEnd); + final D[] copy = ((Object) iClass == (Object) Object[].class) ? (D[]) new Object[newLength] : (D[]) Array.newInstance( + iClass.getComponentType(), newLength); + System.arraycopy(iSource, iBegin, copy, 0, Math.min(iSource.length - iBegin, newLength)); + return copy; + } + + public static byte[] copyOfRange(final byte[] iSource, final int iBegin, final int iEnd) { + final int newLength = iEnd - iBegin; + if (newLength < 0) + throw new IllegalArgumentException(iBegin + " > " + iEnd); + + try { + final byte[] copy = new byte[newLength]; + System.arraycopy(iSource, iBegin, copy, 0, Math.min(iSource.length - iBegin, newLength)); + return copy; + } catch (OutOfMemoryError e) { + OLogManager.instance().error(null, "Error on copying buffer of size %d bytes", e, newLength); + throw e; + } + } + + public static int[] copyOf(final int[] iSource, final int iNewSize) { + final int[] copy = new int[iNewSize]; + System.arraycopy(iSource, 0, copy, 0, Math.min(iSource.length, iNewSize)); + return copy; + } + + /** + * Returns true if an arrays contains a value, otherwise false + */ + public static boolean contains(final int[] iArray, final int iToFind) { + if (iArray == null || iArray.length == 0) + return false; + + for (int e : iArray) + if (e == iToFind) + return true; + + return false; + } + + /** + * Returns true if an arrays contains a value, otherwise false + */ + public static boolean contains(final T[] iArray, final T iToFind) { + if (iArray == null || iArray.length == 0) + return false; + + for (T e : iArray) + if (e != null && e.equals(iToFind)) + return true; + + return false; + } + + public static int hash(final Object[] iArray) { + int hash = 0; + for (Object o : iArray) { + if (o != null) + hash += o.hashCode(); + } + return hash; + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OByteBufferUtils.java b/core/src/main/java/com/orientechnologies/common/util/OByteBufferUtils.java new file mode 100644 index 00000000000..6ffb2009306 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OByteBufferUtils.java @@ -0,0 +1,159 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +import java.nio.ByteBuffer; + +/** + * This class is utility class for split primitive types to separate byte buffers and vice versa. This class is used because we use + * many byte buffers for mmap and there is situation when we need to write value on border of two buffers. + * + * @author Artem Loginov (logart2007@gmail.com) + * @since 5/25/12 6:37 AM + */ +public class OByteBufferUtils { + public static final int SIZE_OF_SHORT = 2; + public static final int SIZE_OF_INT = 4; + public static final int SIZE_OF_LONG = 8; + private static final int SIZE_OF_BYTE_IN_BITS = 8; + private static final int MASK = 0x000000FF; + + /** + * Merge short value from two byte buffer. First byte of short will be extracted from first byte buffer and second from second + * one. + * + * @param buffer + * to read first part of value + * @param buffer1 + * to read second part of value + * @return merged value + */ + public static short mergeShortFromBuffers(final ByteBuffer buffer, final ByteBuffer buffer1) { + short result = 0; + result = (short) (result | (buffer.get() & MASK)); + result = (short) (result << SIZE_OF_BYTE_IN_BITS); + result = (short) (result | (buffer1.get() & MASK)); + return result; + } + + /** + * Merge int value from two byte buffer. First bytes of int will be extracted from first byte buffer and second from second one. + * How many bytes will be read from first buffer determines based on buffer.remaining() value + * + * @param buffer + * to read first part of value + * @param buffer1 + * to read second part of value + * @return merged value + */ + public static int mergeIntFromBuffers(final ByteBuffer buffer, final ByteBuffer buffer1) { + int result = 0; + final int remaining = buffer.remaining(); + for (int i = 0; i < remaining; ++i) { + result = result | (buffer.get() & MASK); + result = result << SIZE_OF_BYTE_IN_BITS; + } + for (int i = 0; i < SIZE_OF_INT - remaining - 1; ++i) { + result = result | (buffer1.get() & MASK); + result = result << SIZE_OF_BYTE_IN_BITS; + } + result = result | (buffer1.get() & MASK); + return result; + } + + /** + * Merge long value from two byte buffer. First bytes of long will be extracted from first byte buffer and second from second one. + * How many bytes will be read from first buffer determines based on buffer.remaining() value + * + * @param buffer + * to read first part of value + * @param buffer1 + * to read second part of value + * @return merged value + */ + public static long mergeLongFromBuffers(final ByteBuffer buffer, final ByteBuffer buffer1) { + long result = 0; + final int remaining = buffer.remaining(); + for (int i = 0; i < remaining; ++i) { + result = result | (MASK & buffer.get()); + result = result << SIZE_OF_BYTE_IN_BITS; + } + for (int i = 0; i < SIZE_OF_LONG - remaining - 1; ++i) { + result = result | (MASK & buffer1.get()); + result = result << SIZE_OF_BYTE_IN_BITS; + } + result = result | (MASK & buffer1.get()); + return result; + } + + /** + * Split short value into two byte buffer. First byte of short will be written to first byte buffer and second to second one. + * + * @param buffer + * to write first part of value + * @param buffer1 + * to write second part of value + */ + public static void splitShortToBuffers(final ByteBuffer buffer, final ByteBuffer buffer1, final short iValue) { + buffer.put((byte) (MASK & (iValue >>> SIZE_OF_BYTE_IN_BITS))); + buffer1.put((byte) (MASK & iValue)); + } + + /** + * Split int value into two byte buffer. First byte of int will be written to first byte buffer and second to second one. How many + * bytes will be written to first buffer determines based on buffer.remaining() value + * + * @param buffer + * to write first part of value + * @param buffer1 + * to write second part of value + */ + public static void splitIntToBuffers(final ByteBuffer buffer, final ByteBuffer buffer1, final int iValue) { + final int remaining = buffer.remaining(); + int i; + for (i = 0; i < remaining; ++i) { + buffer.put((byte) (MASK & (iValue >>> SIZE_OF_BYTE_IN_BITS * (SIZE_OF_INT - i - 1)))); + } + for (int j = 0; j < SIZE_OF_INT - remaining; ++j) { + buffer1.put((byte) (MASK & (iValue >>> SIZE_OF_BYTE_IN_BITS * (SIZE_OF_INT - i - j - 1)))); + } + } + + /** + * Split long value into two byte buffer. First byte of long will be written to first byte buffer and second to second one. How + * many bytes will be written to first buffer determines based on buffer.remaining() value + * + * @param buffer + * to write first part of value + * @param buffer1 + * to write second part of value + */ + public static void splitLongToBuffers(final ByteBuffer buffer, final ByteBuffer buffer1, final long iValue) { + final int remaining = buffer.remaining(); + int i; + for (i = 0; i < remaining; ++i) { + buffer.put((byte) (iValue >> SIZE_OF_BYTE_IN_BITS * (SIZE_OF_LONG - i - 1))); + } + for (int j = 0; j < SIZE_OF_LONG - remaining; ++j) { + buffer1.put((byte) (iValue >> SIZE_OF_BYTE_IN_BITS * (SIZE_OF_LONG - i - j - 1))); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCallable.java b/core/src/main/java/com/orientechnologies/common/util/OCallable.java new file mode 100644 index 00000000000..2e52ee74a46 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCallable.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic callable interface that accepts a parameter. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCallable { + RET call(PAR iArgument); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCallableNoParam.java b/core/src/main/java/com/orientechnologies/common/util/OCallableNoParam.java new file mode 100644 index 00000000000..4a12d539e86 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCallableNoParam.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic callable interface that returns a value. it's similar to Java Callable, but does not throw Exception + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCallableNoParam { + RET call(); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCallableNoParamNoReturn.java b/core/src/main/java/com/orientechnologies/common/util/OCallableNoParamNoReturn.java new file mode 100644 index 00000000000..2c07aafa7f1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCallableNoParamNoReturn.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic callable interface that does not accept parameter and not return any value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCallableNoParamNoReturn { + void call(); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCallableNoReturn.java b/core/src/main/java/com/orientechnologies/common/util/OCallableNoReturn.java new file mode 100644 index 00000000000..dd7aa71b584 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCallableNoReturn.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic callable interface that accepts a parameter and not return any value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCallableNoReturn { + void call(PAR iArgument); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCallableUtils.java b/core/src/main/java/com/orientechnologies/common/util/OCallableUtils.java new file mode 100644 index 00000000000..78c4141cdcd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCallableUtils.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic utility methods for callable. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OCallableUtils { + + public static void executeIgnoringAnyExceptions(final OCallableNoParamNoReturn callback) { + try { + callback.call(); + } catch (Exception e) { + // IGNORE IT ON PURPOSE + } + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OClassLoaderHelper.java b/core/src/main/java/com/orientechnologies/common/util/OClassLoaderHelper.java new file mode 100755 index 00000000000..d112aec89c3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OClassLoaderHelper.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.exception.OConfigurationException; + +public class OClassLoaderHelper { + + /** + * Switch to the OrientDb classloader before lookups on ServiceRegistry for implementation of the given Class. Useful under OSGI + * and generally under applications where jars are loaded by another class loader + * + * @param clazz + * the class to lookup foor + * @return an Iterator on the class implementation + */ + public static synchronized Iterator lookupProviderWithOrientClassLoader(Class clazz) { + + return lookupProviderWithOrientClassLoader(clazz, OClassLoaderHelper.class.getClassLoader()); + } + + public static synchronized Iterator lookupProviderWithOrientClassLoader(Class clazz, + ClassLoader orientClassLoader) { + + final ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(orientClassLoader); + try { + return ServiceLoader.load(clazz).iterator(); + } catch (Exception e) { + OLogManager.instance().warn(null, "Cannot lookup in service registry", e); + throw OException.wrapException(new OConfigurationException("Cannot lookup in service registry"), e); + } finally { + Thread.currentThread().setContextClassLoader(origClassLoader); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCollections.java b/core/src/main/java/com/orientechnologies/common/util/OCollections.java new file mode 100644 index 00000000000..22983d1b5ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCollections.java @@ -0,0 +1,109 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * Set of utility methods to work with collections. + */ +public class OCollections { + /** + * This method is used to find an item in a collection using passed in comparator. Only 0 value (requested object is found) + * returned by comparator is taken into account the rest is ignored. + * + * @param list List in which value should be found. + * @param object Object to find. + * @param comparator Comparator is sued for search. + * @param Type of collection elements. + * + * @return Index of found item or -1 otherwise. + */ + public static int indexOf(final List list, final T object, final Comparator comparator) { + int i = 0; + for (final T item : list) { + if (comparator.compare(item, object) == 0) + return i; + i++; + } + return -1; + } + + /** + * This method is used to find an item in an array. + * + * @param array Array in which value should be found. + * @param object Object to find. + * + * @return Index of found item or -1 otherwise. + */ + public static int indexOf(final Object[] array, final Comparable object) { + for (int i = 0; i < array.length; ++i) { + if (object.compareTo(array[i]) == 0) + // FOUND + return i; + } + return -1; + } + + /** + * This method is used to find a number in an array. + * + * @param array Array of integers in which value should be found. + * @param object number to find. + * + * @return Index of found item or -1 otherwise. + */ + public static int indexOf(final int[] array, final int object) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == object) + // FOUND + return i; + } + return -1; + } + + /** + * Create a string representation of all objects in the given Iterable. example : [value1,value2,value3] + * + * @param iterable + * + * @return String + */ + public static String toString(Iterable iterable) { + final StringBuilder builder = new StringBuilder(512); + builder.append('['); + int cnt = 0; + final Iterator ite = iterable.iterator(); + while (ite.hasNext()) { + if (cnt != 0) { + builder.append(','); + } + cnt++; + final Object obj = ite.next(); + builder.append(obj); + } + builder.append(']'); + return builder.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OCommonConst.java b/core/src/main/java/com/orientechnologies/common/util/OCommonConst.java new file mode 100755 index 00000000000..8e0c42ff871 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OCommonConst.java @@ -0,0 +1,50 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +import com.orientechnologies.orient.core.config.OStorageFileConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.hashindex.local.OHashIndexBucket; +import com.orientechnologies.orient.core.storage.cache.OPageDataVerificationError; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.OPhysicalPosition; + +public final class OCommonConst { + + + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + public static final int[] EMPTY_INT_ARRAY = new int[0]; + public static final OCluster[] EMPTY_CLUSTER_ARRAY = new OCluster[0]; + public static final OIdentifiable[] EMPTY_IDENTIFIABLE_ARRAY = new OIdentifiable[0]; + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public static final OType[] EMPTY_TYPES_ARRAY = new OType[0]; + public static final OPageDataVerificationError[] EMPTY_PAGE_DATA_VERIFICATION_ARRAY = new OPageDataVerificationError[0]; + public static final OHashIndexBucket.Entry[] EMPTY_BUCKET_ENTRY_ARRAY = new OHashIndexBucket.Entry[0]; + public static final OPhysicalPosition[] EMPTY_PHYSICAL_POSITIONS_ARRAY = new OPhysicalPosition[0]; + public static final OStorageFileConfiguration[] EMPTY_FILE_CONFIGURATIONS_ARRAY = new OStorageFileConfiguration[0]; + + + + private OCommonConst() { + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OMemory.java b/core/src/main/java/com/orientechnologies/common/util/OMemory.java new file mode 100644 index 00000000000..03e1fbdd456 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OMemory.java @@ -0,0 +1,279 @@ +/* + * + * * Copyright 2016 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + */ + +package com.orientechnologies.common.util; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OConfigurationException; + +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +/** + * Provides various utilities related to memory management and configuration. + * + * @author Sergey Sitnikov + */ +public class OMemory { + // JVM accepts this option exactly as it appears here, no lowercase/uppercase mixing and additional spacing allowed + private static final String XX_MAX_DIRECT_MEMORY_SIZE = "-XX:MaxDirectMemorySize="; + + /** + * @param unlimitedCap the upper limit on reported memory, if JVM reports unlimited memory. + * + * @return same as {@link Runtime#maxMemory()} except that {@code unlimitedCap} limit is applied if JVM reports + * {@link Long#MAX_VALUE unlimited memory}. + */ + public static long getCappedRuntimeMaxMemory(long unlimitedCap) { + final long jvmMaxMemory = Runtime.getRuntime().maxMemory(); + return jvmMaxMemory == Long.MAX_VALUE ? unlimitedCap : jvmMaxMemory; + } + + /** + * Obtains the total size in bytes of the installed physical memory on this machine. + * Note that on some VMs it's impossible to obtain the physical memory size, in this + * case the return value will {@code -1}. + * + * @return the total physical memory size in bytes or {@code -1} if the size can't be obtained. + */ + public static long getPhysicalMemorySize() { + long osMemory = -1; + + final OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean(); + try { + final Method memorySize = mxBean.getClass().getDeclaredMethod("getTotalPhysicalMemorySize"); + memorySize.setAccessible(true); + osMemory = (Long) memorySize.invoke(mxBean); + } catch (NoSuchMethodException e) { + if (!OLogManager.instance().isDebugEnabled()) + OLogManager.instance().warn(OMemory.class, "Unable to determine the amount of installed RAM."); + else + OLogManager.instance().debug(OMemory.class, "Unable to determine the amount of installed RAM.", e); + } catch (InvocationTargetException e) { + if (!OLogManager.instance().isDebugEnabled()) + OLogManager.instance().warn(OMemory.class, "Unable to determine the amount of installed RAM."); + else + OLogManager.instance().debug(OMemory.class, "Unable to determine the amount of installed RAM.", e); + } catch (IllegalAccessException e) { + if (!OLogManager.instance().isDebugEnabled()) + OLogManager.instance().warn(OMemory.class, "Unable to determine the amount of installed RAM."); + else + OLogManager.instance().debug(OMemory.class, "Unable to determine the amount of installed RAM.", e); + } + + return osMemory; + } + + /** + * Obtains the configured value of the {@code -XX:MaxDirectMemorySize} JVM option in bytes. + * + * @return the configured maximum direct memory size or {@code -1} if no configuration provided. + */ + public static long getConfiguredMaxDirectMemory() { + long maxDirectMemorySize = -1; + + final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + final List vmArgs = runtimeMXBean.getInputArguments(); + for (String arg : vmArgs) + if (arg.startsWith(XX_MAX_DIRECT_MEMORY_SIZE)) { + try { + maxDirectMemorySize = parseVmArgsSize(arg.substring(XX_MAX_DIRECT_MEMORY_SIZE.length())); + } catch (IllegalArgumentException e) { + OLogManager.instance().error(OMemory.class, "Unable to parse the value of -XX:MaxDirectMemorySize option.", e); + } + break; + } + + return maxDirectMemorySize; + } + + /** + * Calculates the total configured maximum size of all OrientDB caches. + * + * @return the total maximum size of all OrientDB caches in bytes. + */ + public static long getMaxCacheMemorySize() { + return OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024; + } + + /** + * Checks the direct memory configuration and emits a warning if configuration is invalid. + */ + public static void checkDirectMemoryConfiguration() { + final long physicalMemory = getPhysicalMemorySize(); + final long maxDirectMemory = getConfiguredMaxDirectMemory(); + + if (maxDirectMemory == -1) { + if (physicalMemory != -1) + OLogManager.instance().warn(OMemory.class, "MaxDirectMemorySize JVM option is not set or has invalid value, " + + "that may cause out of memory errors. Please set the -XX:MaxDirectMemorySize=" + physicalMemory / (1024 * 1024) + + "m option when you start the JVM."); + else + OLogManager.instance().warn(OMemory.class, "MaxDirectMemorySize JVM option is not set or has invalid value, " + + "that may cause out of memory errors. Please set the -XX:MaxDirectMemorySize=m JVM option " + + "when you start the JVM, where is the memory size of this machine in megabytes."); + } else if (maxDirectMemory < 64 * 1024 * 1024) + throw new OConfigurationException("MaxDirectMemorySize JVM option value is too low (" + maxDirectMemory + " bytes)," + + " OrientDB requires at least 64MB of direct memory to function properly. Please tune the value of " + + "-XX:MaxDirectMemorySize JVM option."); + } + + /** + * Checks the OrientDB cache memory configuration and emits a warning if configuration is invalid. + */ + public static void checkCacheMemoryConfiguration() { + final long maxHeapSize = Runtime.getRuntime().maxMemory(); + final long maxCacheSize = getMaxCacheMemorySize(); + final long physicalMemory = getPhysicalMemorySize(); + final long maxDirectMemory = getConfiguredMaxDirectMemory(); + + if (maxDirectMemory != -1 && maxCacheSize > maxDirectMemory) + OLogManager.instance().warn(OMemory.class, "Configured maximum amount of memory available to the cache (" + maxCacheSize + + " bytes) is larger than configured JVM maximum direct memory size (" + maxDirectMemory + " bytes). That may cause " + + "out of memory errors, please tune the configuration up. Use the -XX:MaxDirectMemorySize JVM option to raise the JVM " + + "maximum direct memory size or storage.diskCache.bufferSize OrientDB option to lower memory requirements of the " + + "cache."); + + if (maxHeapSize != Long.MAX_VALUE && physicalMemory != -1 && maxHeapSize + maxCacheSize > physicalMemory) + OLogManager.instance().warn(OMemory.class, + "The sum of the configured JVM maximum heap size (" + maxHeapSize + " bytes) " + "and the OrientDB maximum cache size (" + + maxCacheSize + " bytes) is larger than the available physical memory size " + "(" + physicalMemory + + " bytes). That may cause out of memory errors, please tune the configuration up. Use the " + + "-Xmx JVM option to lower the JVM maximum heap memory size or storage.diskCache.bufferSize OrientDB option to " + + "lower memory requirements of the cache."); + } + + /** + * Checks the {@link com.orientechnologies.common.directmemory.OByteBufferPool} configuration and emits a warning + * if configuration is invalid. + */ + public static void checkByteBufferPoolConfiguration() { + final long maxDirectMemory = OMemory.getConfiguredMaxDirectMemory(); + final long memoryChunkSize = OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong(); + final long maxCacheSize = getMaxCacheMemorySize(); + + if (maxDirectMemory != -1 && memoryChunkSize > maxDirectMemory) + OLogManager.instance().warn(OMemory.class, + "The configured memory chunk size (" + memoryChunkSize + " bytes) is larger than the configured maximum amount of " + + "JVM direct memory (" + maxDirectMemory + " bytes). That may cause out of memory errors, please tune the " + + "configuration up. Use the -XX:MaxDirectMemorySize JVM option to raise the JVM maximum direct memory size " + + "or memory.chunk.size OrientDB option to lower memory chunk size."); + + if (memoryChunkSize > maxCacheSize) + OLogManager.instance().warn(OMemory.class, + "The configured memory chunk size (" + memoryChunkSize + " bytes) is larger than the configured maximum cache size (" + + maxCacheSize + " bytes). That may cause overallocation of a memory which will be wasted, please tune the " + + "configuration up. Use the storage.diskCache.bufferSize OrientDB option to raise the cache memory size " + + "or memory.chunk.size OrientDB option to lower memory chunk size."); + } + + /** + * Tries to fix some common cache/memory configuration problems: + *
    + *
  • Cache size is larger than direct memory size.
  • + *
  • Memory chunk size is larger than cache size.
  • + *
      + */ + public static void fixCommonConfigurationProblems() { + final long maxDirectMemory = OMemory.getConfiguredMaxDirectMemory(); + long diskCacheSize = OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong(); + + if (maxDirectMemory != -1) { + final long maxDiskCacheSize = Math.min(maxDirectMemory / 1024 / 1024, Integer.MAX_VALUE); + + if (diskCacheSize > maxDiskCacheSize) { + OLogManager.instance() + .info(OGlobalConfiguration.class, "Lowering disk cache size from %,dMB to %,dMB.", diskCacheSize, maxDiskCacheSize); + OGlobalConfiguration.DISK_CACHE_SIZE.setValue(maxDiskCacheSize); + diskCacheSize = maxDiskCacheSize; + } + } + + final int max32BitCacheSize = 512; + if (getJavaBitWidth() == 32 && diskCacheSize > max32BitCacheSize) { + OLogManager.instance() + .info(OGlobalConfiguration.class, "32 bit JVM is detected. Lowering disk cache size from %,dMB to %,dMB.", diskCacheSize, + max32BitCacheSize); + OGlobalConfiguration.DISK_CACHE_SIZE.setValue(max32BitCacheSize); + } + + if (OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong() + > OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024) { + final long newChunkSize = Math.min(OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024, Integer.MAX_VALUE); + OLogManager.instance().info(OGlobalConfiguration.class, "Lowering memory chunk size from %,dB to %,dB.", + OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong(), newChunkSize); + OGlobalConfiguration.MEMORY_CHUNK_SIZE.setValue(newChunkSize); + } + } + + private static int getJavaBitWidth() { + // Figure out whether bit width of running JVM + // Most of JREs support property "sun.arch.data.model" which is exactly what we need here + String dataModel = System.getProperty("sun.arch.data.model", "64"); // By default assume 64bit + int size = 64; + try { + size = Integer.parseInt(dataModel); + } catch (Throwable t) { + // Ignore + } + return size; + } + + /** + * Parses the size specifier formatted in the JVM style, like 1024k or 4g. + * Following units are supported: k or K – kilobytes, m or M – megabytes, g or G – gigabytes. + * If no unit provided, it is bytes. + * + * @param text the text to parse. + * + * @return the parsed size value. + * + * @throws IllegalArgumentException if size specifier is not recognized as valid. + */ + public static long parseVmArgsSize(String text) throws IllegalArgumentException { + if (text == null) + throw new IllegalArgumentException("text can't be null"); + if (text.length() == 0) + throw new IllegalArgumentException("text can't be empty"); + + final char unit = text.charAt(text.length() - 1); + if (Character.isDigit(unit)) + return Long.parseLong(text); + + final long value = Long.parseLong(text.substring(0, text.length() - 1)); + switch (Character.toLowerCase(unit)) { + case 'g': + return value * 1024 * 1024 * 1024; + case 'm': + return value * 1024 * 1024; + case 'k': + return value * 1024; + } + + throw new IllegalArgumentException("text '" + text + "' is not a size specifier."); + } + + private OMemory() { + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OMultiKey.java b/core/src/main/java/com/orientechnologies/common/util/OMultiKey.java new file mode 100644 index 00000000000..010c54fcedd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OMultiKey.java @@ -0,0 +1,89 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * Multiple key container that is used as key for {@link java.util.Map}. + * Despite of the {@link java.util.List} order of keys does not matter, but unlike {@link java.util.Set} can contain + * duplicate values. + * + */ +public class OMultiKey { + private final Collection keys; + private final int hash; + + public OMultiKey(final Collection keys) { + this.keys = new ArrayList(keys); + hash = generateHashCode(keys); + } + + private int generateHashCode(final Collection objects) { + int total = 0; + for (final Object object : objects) { + total ^= object.hashCode(); + } + return total; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return hash; + } + + /** + * Objects are equals if they contain the same amount of keys and these keys are equals. + * Order of keys does not matter. + * + * @param o obj the reference object with which to compare. + * @return true if this object is the same as the obj + * argument; false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final OMultiKey oMultiKey = (OMultiKey) o; + + if(keys.size() != oMultiKey.keys.size()) + return false; + + for (final Object inKey : keys) { + if (!oMultiKey.keys.contains(inKey)) + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "OMultiKey " + keys + ""; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OPair.java b/core/src/main/java/com/orientechnologies/common/util/OPair.java new file mode 100755 index 00000000000..66c29ba8fd1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OPair.java @@ -0,0 +1,135 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Keeps a pair of values as Key/Value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * @param + * Key + * @param + * Value + * @see OTriple + */ +public class OPair implements Entry, Comparable>, Serializable { + public K key; + public V value; + + public OPair() { + } + + public OPair(final K iKey, final V iValue) { + key = iKey; + value = iValue; + } + + public OPair(final Entry iSource) { + key = iSource.getKey(); + value = iSource.getValue(); + } + + public void init(final K iKey, final V iValue) { + key = iKey; + value = iValue; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(final V iValue) { + V oldValue = value; + value = iValue; + return oldValue; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(512); + buffer.append(key); + buffer.append(':'); + + if (value == null || !value.getClass().isArray()) + buffer.append(value); + else + buffer.append(Arrays.toString((Object[]) value)); + + return buffer.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OPair other = (OPair) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } + + public int compareTo(final OPair o) { + return key.compareTo(o.key); + } + + public static , V> Map convertToMap(final List> iValues) { + final HashMap result = new HashMap(iValues.size()); + for (OPair p : iValues) + result.put(p.getKey(), p.getValue()); + + return result; + } + + public static , V> List> convertFromMap(final Map iValues) { + final List> result = new ArrayList>(iValues.size()); + for (Entry p : iValues.entrySet()) + result.add(new OPair(p.getKey(), p.getValue())); + + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OPatternConst.java b/core/src/main/java/com/orientechnologies/common/util/OPatternConst.java new file mode 100644 index 00000000000..88011cc6983 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OPatternConst.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +import java.util.regex.Pattern; + +public final class OPatternConst { + + public static final Pattern PATTERN_COMMA_SEPARATED = Pattern.compile("\\s*,\\s*"); + public static final Pattern PATTERN_SPACES = Pattern.compile("\\s+"); + public static final Pattern PATTERN_FETCH_PLAN = Pattern.compile(".*:-?\\d+"); + public static final Pattern PATTERN_SINGLE_SPACE = Pattern.compile(" "); + public static final Pattern PATTERN_NUMBERS = Pattern.compile("[^\\d]"); + public static final Pattern PATTERN_RID = Pattern.compile("#(-?[0-9]+):(-?[0-9]+)"); + public static final Pattern PATTERN_DIACRITICAL_MARKS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + public static final Pattern PATTERN_AMP = Pattern.compile("&"); + public static final Pattern PATTERN_REST_URL = Pattern.compile("\\{[a-zA-Z0-9%:]*\\}"); + + private OPatternConst() { + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/ORawPair.java b/core/src/main/java/com/orientechnologies/common/util/ORawPair.java new file mode 100644 index 00000000000..1d990901bff --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/ORawPair.java @@ -0,0 +1,62 @@ +/* + * * Copyright 2016 OrientDB LTD (info(at)orientdb.com) + * * Licensed 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. + * * + * * For more information: http://orientdb.com + * + */ +package com.orientechnologies.common.util; + +/** + * Container for pair of non null objects. + * + * @author Anrey Lomakin + * @since 2.2 + */ +public class ORawPair { + private final V1 first; + private final V2 second; + + public ORawPair(V1 first, V2 second) { + this.first = first; + this.second = second; + } + + public V1 getFirst() { + return first; + } + + public V2 getSecond() { + return second; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ORawPair oRawPair = (ORawPair) o; + + if (!first.equals(oRawPair.first)) + return false; + return second.equals(oRawPair.second); + + } + + @Override + public int hashCode() { + int result = first.hashCode(); + result = 31 * result + second.hashCode(); + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OResettable.java b/core/src/main/java/com/orientechnologies/common/util/OResettable.java new file mode 100644 index 00000000000..bbd9d58f111 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OResettable.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +/** + * Interface that support reset() + */ +public interface OResettable { + public void reset(); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OService.java b/core/src/main/java/com/orientechnologies/common/util/OService.java new file mode 100644 index 00000000000..04c6abfd141 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OService.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Generic Service interface. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OService { + public String getName(); + + public void startup(); + + public void shutdown(); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OSizeable.java b/core/src/main/java/com/orientechnologies/common/util/OSizeable.java new file mode 100644 index 00000000000..4af5b2430a1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OSizeable.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +/** + * Interface that support size() + */ +public interface OSizeable { + int size(); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OSupportsContains.java b/core/src/main/java/com/orientechnologies/common/util/OSupportsContains.java new file mode 100644 index 00000000000..533a6a4f077 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OSupportsContains.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.common.util; + +/** + * Interface that implement a contains() + */ +public interface OSupportsContains { + boolean supportsFastContains(); + + boolean contains(T value); +} diff --git a/core/src/main/java/com/orientechnologies/common/util/OTriple.java b/core/src/main/java/com/orientechnologies/common/util/OTriple.java new file mode 100644 index 00000000000..595273f257a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/common/util/OTriple.java @@ -0,0 +1,97 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.common.util; + +/** + * Structure to handle a triple of values configured as a key and a Pair as value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * @see OPair + */ +public class OTriple, V extends Comparable, SV> implements Comparable> { + public K key; + public OPair value; + + public OTriple() { + } + + public OTriple(final K iKey, final V iValue, final SV iSubValue) { + init(iKey, iValue, iSubValue); + } + + public void init(final K iKey, final V iValue, final SV iSubValue) { + key = iKey; + value = new OPair(iValue, iSubValue); + } + + public K getKey() { + return key; + } + + public OPair getValue() { + return value; + } + + public OPair setValue(final OPair iValue) { + final OPair oldValue = value; + value = iValue; + return oldValue; + } + + public void setSubValue(final SV iSubValue) { + final OPair oldValue = value; + value.setValue(iSubValue); + } + + @Override + public String toString() { + return key + ":" + value.getKey() + "/" + value.getValue(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OTriple other = (OTriple) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } + + public int compareTo(final OTriple o) { + return key.compareTo(o.key); + } +} diff --git a/core/src/main/java/com/orientechnologies/nio/CLibrary.java b/core/src/main/java/com/orientechnologies/nio/CLibrary.java new file mode 100755 index 00000000000..5b6fa1b7804 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/nio/CLibrary.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.nio; + +/** + * @author Andrey Lomakin + * @since 5/6/13 + */ +public interface CLibrary { + void memoryMove(long src, long dest, long len); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/.DS_Store b/core/src/main/java/com/orientechnologies/orient/core/.DS_Store new file mode 100644 index 00000000000..2aa8178b13f Binary files /dev/null and b/core/src/main/java/com/orientechnologies/orient/core/.DS_Store differ diff --git a/core/src/main/java/com/orientechnologies/orient/core/OOrientListener.java b/core/src/main/java/com/orientechnologies/orient/core/OOrientListener.java new file mode 100644 index 00000000000..79d25f921cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OOrientListener.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core; + +import com.orientechnologies.orient.core.storage.OStorage; + +/** + * Listener Interface for basic Orient events. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OOrientListener extends OOrientShutdownListener { + void onShutdown(); + + void onStorageRegistered(final OStorage storage); + + void onStorageUnregistered(final OStorage storage); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/OOrientListenerAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/OOrientListenerAbstract.java new file mode 100644 index 00000000000..9f57c6b0749 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OOrientListenerAbstract.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core; + +import com.orientechnologies.orient.core.storage.OStorage; + +/** + * Abstract implementation of OOrientListener interface. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OOrientListenerAbstract implements OOrientListener, OOrientStartupListener, OOrientShutdownListener { + @Override + public void onStartup() { + } + + @Override + public void onStorageRegistered(OStorage iStorage) { + } + + @Override + public void onStorageUnregistered(OStorage iStorage) { + } + + @Override + public void onShutdown() { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/OOrientShutdownListener.java b/core/src/main/java/com/orientechnologies/orient/core/OOrientShutdownListener.java new file mode 100644 index 00000000000..7bb8e54f53f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OOrientShutdownListener.java @@ -0,0 +1,9 @@ +package com.orientechnologies.orient.core; + +/** + * @author Andrey Lomakin Andrey Lomakin + * @since 09/01/15 + */ +public interface OOrientShutdownListener { + void onShutdown(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/OOrientStartupListener.java b/core/src/main/java/com/orientechnologies/orient/core/OOrientStartupListener.java new file mode 100644 index 00000000000..79b4e8718c8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OOrientStartupListener.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core; + +/** + * Listener Interface to catch Orient startup event. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OOrientStartupListener { + public void onStartup(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/OSignalHandler.java b/core/src/main/java/com/orientechnologies/orient/core/OSignalHandler.java new file mode 100644 index 00000000000..8566a59be3b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OSignalHandler.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import sun.misc.Signal; +import sun.misc.SignalHandler; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map.Entry; + +@SuppressWarnings("restriction") +public class OSignalHandler implements SignalHandler { + private Hashtable redefinedHandlers = new Hashtable(4); + private List listeners = new ArrayList(); + + public interface OSignalListener { + void onSignal(Signal signal); + } + + public OSignalHandler() { + } + + public void registerListener(final OSignalListener listener) { + listeners.add(listener); + } + + public void unregisterListener(final OSignalListener listener) { + listeners.remove(listener); + } + + public void listenTo(final String name, final SignalHandler iListener) { + Signal signal = new Signal(name); + SignalHandler redefinedHandler = Signal.handle(signal, iListener); + if (redefinedHandler != null) { + redefinedHandlers.put(signal, redefinedHandler); + } + } + + public void handle(final Signal signal) { + OLogManager.instance().warn(this, "Received signal: %s", signal); + + final String s = signal.toString().trim(); + + if (Orient.instance().isSelfManagedShutdown() && (s.equals("SIGKILL") || s.equals("SIGHUP") || s.equals("SIGINT") || s + .equals("SIGTERM"))) { + Orient.instance().shutdown(); + System.exit(1); + } else if (s.equals("SIGTRAP")) { + System.out.println(); + OGlobalConfiguration.dumpConfiguration(System.out); + System.out.println(); + Orient.instance().getProfiler().dump(System.out); + System.out.println(); + System.out.println(Orient.instance().getProfiler().threadDump()); + } else { + SignalHandler redefinedHandler = redefinedHandlers.get(signal); + if (redefinedHandler != null) { + redefinedHandler.handle(signal); + } + } + + for (OSignalListener l : listeners) + l.onSignal(signal); + } + + public void installDefaultSignals() { + installDefaultSignals(this); + } + + public void installDefaultSignals(final SignalHandler iListener) { + // listenTo("HUP", iListener); // DISABLED HUB BECAUSE ON WINDOWS IT'S USED INTERNALLY AND CAUSED JVM KILL + // listenTo("KILL",iListener); + + try { + listenTo("INT", iListener); + } catch (IllegalArgumentException e) { + // NOT AVAILABLE + } + try { + listenTo("TERM", iListener); + } catch (IllegalArgumentException e) { + // NOT AVAILABLE + } + try { + listenTo("TRAP", iListener); + } catch (IllegalArgumentException e) { + // NOT AVAILABLE + } + } + + public void cancel() { + for (Entry entry : redefinedHandlers.entrySet()) { + try { + // re-install the original handler we replaced + Signal.handle(entry.getKey(), entry.getValue()); + } catch (IllegalStateException e) { + // not expected as we were able to redefine it earlier, but just in case + } + } + redefinedHandlers.clear(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/Orient.java b/core/src/main/java/com/orientechnologies/orient/core/Orient.java new file mode 100755 index 00000000000..68024386a68 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/Orient.java @@ -0,0 +1,1052 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core; + +import com.orientechnologies.common.directmemory.OByteBufferPool; +import com.orientechnologies.common.io.OFileUtils; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.listener.OListenerManger; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.parser.OSystemVariableResolver; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.common.profiler.OProfilerStub; +import com.orientechnologies.common.util.OClassLoaderHelper; +import com.orientechnologies.orient.core.cache.OLocalRecordCacheFactory; +import com.orientechnologies.orient.core.cache.OLocalRecordCacheFactoryImpl; +import com.orientechnologies.orient.core.command.script.OScriptManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategyFactory; +import com.orientechnologies.orient.core.db.ODatabaseLifecycleListener; +import com.orientechnologies.orient.core.db.ODatabaseThreadLocalFactory; +import com.orientechnologies.orient.core.engine.OEngine; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.record.ORecordFactoryManager; +import com.orientechnologies.orient.core.security.OSecuritySystem; +import com.orientechnologies.orient.core.shutdown.OShutdownHandler; +import com.orientechnologies.orient.core.storage.OIdentifiableStorage; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class Orient extends OListenerManger { + public static final String ORIENTDB_HOME = "ORIENTDB_HOME"; + public static final String URL_SYNTAX = "::[?=[&]]*"; + + private static final Orient instance = new Orient(); + private static volatile boolean registerDatabaseByPath = false; + + private final ConcurrentMap engines = new ConcurrentHashMap(); + private final ConcurrentMap storages = new ConcurrentHashMap(); + private final ConcurrentHashMap storageIds = new ConcurrentHashMap(); + + private final Map dbLifecycleListeners = new LinkedHashMap(); + private final OScriptManager scriptManager = new OScriptManager(); + private final ThreadGroup threadGroup; + private final ReadWriteLock engineLock = new ReentrantReadWriteLock(); + private final ORecordConflictStrategyFactory recordConflictStrategy = new ORecordConflictStrategyFactory(); + private final ReferenceQueue removedStartupListenersQueue = new ReferenceQueue(); + private final ReferenceQueue removedShutdownListenersQueue = new ReferenceQueue(); + private final Set startupListeners = Collections + .newSetFromMap(new ConcurrentHashMap()); + private final Set> weakStartupListeners = Collections + .newSetFromMap(new ConcurrentHashMap, Boolean>()); + private final Set> weakShutdownListeners = Collections + .newSetFromMap(new ConcurrentHashMap, Boolean>()); + + private final PriorityQueue shutdownHandlers = new PriorityQueue(11, + new Comparator() { + @Override + public int compare(OShutdownHandler handlerOne, OShutdownHandler handlerTwo) { + if (handlerOne.getPriority() > handlerTwo.getPriority()) + return 1; + + if (handlerOne.getPriority() < handlerTwo.getPriority()) + return -1; + + return 0; + } + }); + + private final OLocalRecordCacheFactory localRecordCache = new OLocalRecordCacheFactoryImpl(); + + static { + instance.startup(); + } + + private final String os; + + private volatile Timer timer; + private volatile ORecordFactoryManager recordFactoryManager = new ORecordFactoryManager(); + private OrientShutdownHook shutdownHook; + private volatile OProfiler profiler; + private ODatabaseThreadLocalFactory databaseThreadFactory; + private volatile boolean active = false; + private ThreadPoolExecutor workers; + private OSignalHandler signalHandler; + private volatile OSecuritySystem security; + private boolean runningDistributed = false; + + private static class WeakHashSetValueHolder extends WeakReference { + private final int hashCode; + + private WeakHashSetValueHolder(T referent, ReferenceQueue q) { + super(referent, q); + this.hashCode = referent.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + WeakHashSetValueHolder that = (WeakHashSetValueHolder) o; + + if (hashCode != that.hashCode) + return false; + + final T thisObject = get(); + final Object thatObject = that.get(); + + if (thisObject == null && thatObject == null) + return super.equals(that); + else if (thisObject != null && thatObject != null) + return thisObject.equals(thatObject); + + return false; + } + } + + protected Orient() { + super(true); + this.os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + threadGroup = new ThreadGroup("OrientDB"); + threadGroup.setDaemon(false); + } + + public static Orient instance() { + return instance; + } + + public static String getHomePath() { + String v = System.getProperty("orient.home"); + + if (v == null) + v = OSystemVariableResolver.resolveVariable(ORIENTDB_HOME); + + return OFileUtils.getPath(v); + } + + public static String getTempPath() { + return OFileUtils.getPath(System.getProperty("java.io.tmpdir") + "/orientdb/"); + } + + /** + * Tells if to register database by path. Default is false. Setting to true allows to have multiple databases in different path + * with the same name. + * + * @see #setRegisterDatabaseByPath(boolean) + */ + public static boolean isRegisterDatabaseByPath() { + return registerDatabaseByPath; + } + + /** + * Register database by path. Default is false. Setting to true allows to have multiple databases in different path with the same + * name. + */ + public static void setRegisterDatabaseByPath(final boolean iValue) { + registerDatabaseByPath = iValue; + } + + public ORecordConflictStrategyFactory getRecordConflictStrategy() { + return recordConflictStrategy; + } + + public Orient startup() { + engineLock.writeLock().lock(); + try { + if (active) + // ALREADY ACTIVE + return this; + + if (timer == null) + timer = new Timer(true); + + profiler = new OProfilerStub(); + + shutdownHook = new OrientShutdownHook(); + if (signalHandler == null) { + signalHandler = new OSignalHandler(); + signalHandler.installDefaultSignals(); + } + + final int cores = Runtime.getRuntime().availableProcessors(); + + workers = new ThreadPoolExecutor(cores, cores * 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue(cores * 500) { + @Override + public boolean offer(Runnable e) { + // turn offer() and add() into a blocking calls (unless interrupted) + try { + put(e); + return true; + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + return false; + } + }); + + registerEngines(); + + if (OGlobalConfiguration.ENVIRONMENT_DUMP_CFG_AT_STARTUP.getValueAsBoolean()) + OGlobalConfiguration.dumpConfiguration(System.out); + + active = true; + + for (OOrientStartupListener l : startupListeners) + try { + if (l != null) + l.onStartup(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on startup", e); + } + + purgeWeakStartupListeners(); + for (final WeakHashSetValueHolder wl : weakStartupListeners) + try { + if (wl != null) { + final OOrientStartupListener l = wl.get(); + if (l != null) + l.onStartup(); + } + + } catch (Exception e) { + OLogManager.instance().error(this, "Error on startup", e); + } + + initShutdownQueue(); + } finally { + engineLock.writeLock().unlock(); + } + + return this; + } + + /** + * Add handler which will be executed during {@link #shutdown()} call. + * + * @param shutdownHandler Shutdown handler instance. + */ + public void addShutdownHandler(OShutdownHandler shutdownHandler) { + engineLock.writeLock().lock(); + try { + shutdownHandlers.add(shutdownHandler); + } finally { + engineLock.writeLock().unlock(); + } + } + + /** + * Adds shutdown handlers in order which will be used during execution of shutdown. + */ + private void initShutdownQueue() { + addShutdownHandler(new OShutdownWorkersHandler()); + addShutdownHandler(new OShutdownEnginesHandler()); + addShutdownHandler(new OShutdownPendingThreadsHandler()); + addShutdownHandler(new OShutdownProfilerHandler()); + addShutdownHandler(new OShutdownCallListenersHandler()); + } + + /** + * Shutdown whole OrientDB ecosystem. Usually is called during JVM shutdown by JVM shutdown handler. During shutdown all handlers + * which were registered by the call of {@link #addShutdownHandler(OShutdownHandler)} are called together with pre-registered + * system shoutdown handlers according to their priority. + * + * @see OShutdownWorkersHandler + * @see + */ + private void registerEngines() { + ClassLoader classLoader = Orient.class.getClassLoader(); + + Iterator engines = OClassLoaderHelper.lookupProviderWithOrientClassLoader(OEngine.class, classLoader); + + OEngine engine = null; + while (engines.hasNext()) { + try { + engine = engines.next(); + registerEngine(engine); + } catch (IllegalArgumentException e) { + if (engine != null) + OLogManager.instance().debug(this, "Failed to replace engine " + engine.getName()); + } + } + } + + public Orient shutdown() { + engineLock.writeLock().lock(); + try { + if (!active) + return this; + + active = false; + + OLogManager.instance().info(this, "Orient Engine is shutting down..."); + for (OShutdownHandler handler : shutdownHandlers) { + try { + OLogManager.instance().debug(this, "Shutdown handler %s is going to be called", handler); + handler.shutdown(); + OLogManager.instance().debug(this, "Shutdown handler %s completed", handler); + } catch (Exception e) { + OLogManager.instance().error(this, "Exception during calling of shutdown handler %s", handler); + } + } + + shutdownHandlers.clear(); + OLogManager.instance().info(this, "OrientDB Engine shutdown complete"); + OLogManager.instance().flush(); + } finally { + try { + removeShutdownHook(); + } finally { + try { + removeSignalHandler(); + } finally { + engineLock.writeLock().unlock(); + } + } + } + + return this; + } + + public void scheduleTask(final TimerTask task, final long delay, final long period) { + engineLock.readLock().lock(); + try { + if (active) { + if (period > 0) + timer.schedule(task, delay, period); + else + timer.schedule(task, delay); + } else + OLogManager.instance().warn(this, "OrientDB engine is down. Task will not be scheduled."); + } finally { + engineLock.readLock().unlock(); + } + } + + public void scheduleTask(final TimerTask task, final Date firstTime, final long period) { + engineLock.readLock().lock(); + try { + if (active) + if (period > 0) + timer.schedule(task, firstTime, period); + else + timer.schedule(task, firstTime); + else + OLogManager.instance().warn(this, "OrientDB engine is down. Task will not be scheduled."); + } finally { + engineLock.readLock().unlock(); + } + } + + public void closeAllStorages() { + shutdownAllStorages(); + } + + private void shutdownAllStorages() { + engineLock.writeLock().lock(); + try { + // CLOSE ALL THE STORAGES + final List storagesCopy = new ArrayList(storages.values()); + for (OStorage stg : storagesCopy) { + try { + OLogManager.instance().info(this, "- shutdown storage: " + stg.getName() + "..."); + stg.shutdown(); + } catch (Throwable e) { + OLogManager.instance().warn(this, "-- error on shutdown storage", e); + } + } + storages.clear(); + } finally { + engineLock.writeLock().unlock(); + } + } + + public boolean isActive() { + return active; + } + + /** + * @deprecated This method is not thread safe. Use {@link #submit(java.util.concurrent.Callable)} instead. + */ + @Deprecated + public ThreadPoolExecutor getWorkers() { + return workers; + } + + public Future submit(final Runnable runnable) { + engineLock.readLock().lock(); + try { + if (active) + return workers.submit(runnable); + else { + OLogManager.instance().warn(this, "OrientDB engine is down. Task will not be submitted."); + throw new IllegalStateException("OrientDB engine is down. Task will not be submitted."); + } + } finally { + engineLock.readLock().unlock(); + } + } + + public Future submit(final Callable callable) { + engineLock.readLock().lock(); + try { + if (active) + return workers.submit(callable); + else { + OLogManager.instance().warn(this, "OrientDB engine is down. Task will not be submitted."); + throw new IllegalStateException("OrientDB engine is down. Task will not be submitted."); + } + } finally { + engineLock.readLock().unlock(); + } + } + + public OStorage loadStorage(String iURL) { + if (iURL == null || iURL.length() == 0) + throw new IllegalArgumentException("URL missed"); + + if (iURL.endsWith("/")) + iURL = iURL.substring(0, iURL.length() - 1); + + // SEARCH FOR ENGINE + int pos = iURL.indexOf(':'); + if (pos <= 0) + throw new OConfigurationException( + "Error in database URL: the engine was not specified. Syntax is: " + URL_SYNTAX + ". URL was: " + iURL); + + final String engineName = iURL.substring(0, pos); + + engineLock.readLock().lock(); + try { + final OEngine engine = engines.get(engineName.toLowerCase(Locale.ENGLISH)); + + if (engine == null) + throw new OConfigurationException( + "Error on opening database: the engine '" + engineName + "' was not found. URL was: " + iURL + + ". Registered engines are: " + engines.keySet()); + + if (!engine.isRunning()) { + final List knownEngines = new ArrayList(engines.keySet()); + if (!startEngine(engine)) + throw new OConfigurationException( + "Error on opening database: the engine '" + engineName + "' was unable to start. URL was: " + iURL + + ". Registered engines was: " + knownEngines); + } + + // SEARCH FOR DB-NAME + iURL = iURL.substring(pos + 1); + + if (isWindowsOS()) { + // WINDOWS ONLY: REMOVE DOUBLE SLASHES NOT AS PREFIX (WINDOWS PATH COULD NEED STARTING FOR "\\". EXAMPLE: "\\mydrive\db"). + // AT + // THIS LEVEL BACKSLASHES ARRIVES AS SLASHES + iURL = iURL.charAt(0) + iURL.substring(1).replace("//", "/"); + } else + // REMOVE ANY // + iURL = iURL.replace("//", "/"); + + pos = iURL.indexOf('?'); + + Map parameters = null; + String dbPath; + if (pos > 0) { + dbPath = iURL.substring(0, pos); + iURL = iURL.substring(pos + 1); + + // PARSE PARAMETERS + parameters = new HashMap(); + String[] pairs = iURL.split("&"); + String[] kv; + for (String pair : pairs) { + kv = pair.split("="); + if (kv.length < 2) + throw new OConfigurationException( + "Error on opening database: parameter has no value. Syntax is: " + URL_SYNTAX + ". URL was: " + iURL); + parameters.put(kv[0], kv[1]); + } + } else + dbPath = iURL; + + if (registerDatabaseByPath) { + try { + dbPath = new File(dbPath).getCanonicalPath(); + } catch (IOException e) { + // IGNORE IT + } + } + + final String dbName = registerDatabaseByPath ? dbPath : engine.getNameFromPath(dbPath); + + final String dbNameCaseInsensitive = dbName.toLowerCase(Locale.ENGLISH); + + OStorage storage; + // SEARCH IF ALREADY USED + storage = storages.get(dbNameCaseInsensitive); + if (storage == null) { + // NOT FOUND: CREATE IT + + do { + storage = engine.createStorage(dbPath, parameters); + } while ((storage instanceof OIdentifiableStorage) + && storageIds.putIfAbsent(((OIdentifiableStorage) storage).getId(), Boolean.TRUE) != null); + + final OStorage oldStorage = storages.putIfAbsent(dbNameCaseInsensitive, storage); + if (oldStorage != null) + storage = oldStorage; + + for (OOrientListener l : browseListeners()) + l.onStorageRegistered(storage); + } + + return storage; + } finally { + engineLock.readLock().unlock(); + } + } + + public boolean isWindowsOS() { + return os.contains("win"); + } + + public OStorage getStorage(final String name) { + if (name == null) + throw new IllegalArgumentException("Storage name is null"); + + engineLock.readLock().lock(); + try { + return storages.get(name.toLowerCase(Locale.ENGLISH)); + } finally { + engineLock.readLock().unlock(); + } + } + + protected void registerEngine(final OEngine iEngine) throws IllegalArgumentException { + OEngine oEngine = engines.get(iEngine.getName()); + + if (oEngine != null) { + if (!oEngine.getClass().isAssignableFrom(iEngine.getClass())) { + throw new IllegalArgumentException("Cannot replace storage " + iEngine.getName()); + } + } + engines.put(iEngine.getName(), iEngine); + } + + /** + * Returns the engine by its name. + * + * @param engineName Engine name to retrieve + * + * @return OEngine instance of found, otherwise null + */ + public OEngine getEngine(final String engineName) { + engineLock.readLock().lock(); + try { + return engines.get(engineName); + } finally { + engineLock.readLock().unlock(); + } + } + + /** + * Obtains an {@link OEngine engine} instance with the given {@code engineName}, if it is {@link OEngine#isRunning() running}. + * + * @param engineName the name of the engine to obtain. + * + * @return the obtained engine instance or {@code null} if no such engine known or the engine is not running. + */ + public OEngine getEngineIfRunning(final String engineName) { + engineLock.readLock().lock(); + try { + final OEngine engine = engines.get(engineName); + return engine == null || !engine.isRunning() ? null : engine; + } finally { + engineLock.readLock().unlock(); + } + } + + /** + * Obtains a {@link OEngine#isRunning() running} {@link OEngine engine} instance with the given {@code engineName}. + * If engine is not running, starts it. + * + * @param engineName the name of the engine to obtain. + * + * @return the obtained running engine instance, never {@code null}. + * + * @throws IllegalStateException if an engine with the given is not found or failed to start. + */ + public OEngine getRunningEngine(final String engineName) { + engineLock.readLock().lock(); + try { + OEngine engine = engines.get(engineName); + if (engine == null) + throw new IllegalStateException("Engine '" + engineName + "' is not found."); + + if (!engine.isRunning() && !startEngine(engine)) + throw new IllegalStateException("Engine '" + engineName + "' is failed to start."); + + return engine; + } finally { + engineLock.readLock().unlock(); + } + } + + public Set getEngines() { + engineLock.readLock().lock(); + try { + return Collections.unmodifiableSet(engines.keySet()); + } finally { + engineLock.readLock().unlock(); + } + } + + public void unregisterStorageByName(final String name) { + if (name == null) + throw new IllegalArgumentException("Storage name is null"); + + final String dbName = registerDatabaseByPath ? name : OIOUtils.getRelativePathIfAny(name, null); + final OStorage stg = storages.get(dbName.toLowerCase(Locale.ENGLISH)); + unregisterStorage(stg); + } + + public void unregisterStorage(final OStorage storage) { + if (!active) + // SHUTDOWNING OR NOT ACTIVE: RETURN + return; + + if (storage == null) + return; + + engineLock.writeLock().lock(); + try { + // UNREGISTER ALL THE LISTENER ONE BY ONE AVOIDING SELF-RECURSION BY REMOVING FROM THE LIST + final Iterable listenerCopy = getListenersCopy(); + for (final OOrientListener l : listenerCopy) { + unregisterListener(l); + l.onStorageUnregistered(storage); + } + + final List storagesToRemove = new ArrayList(); + + for (Entry s : storages.entrySet()) { + if (s.getValue().equals(storage)) + storagesToRemove.add(s.getKey()); + } + + for (String dbName : storagesToRemove) + storages.remove(dbName.toLowerCase(Locale.ENGLISH)); + + // UNREGISTER STORAGE FROM ENGINES IN CASE IS CACHED + for (OEngine engine : engines.values()) { + engine.removeStorage(storage); + } + + } finally { + engineLock.writeLock().unlock(); + } + } + + public Collection getStorages() { + engineLock.readLock().lock(); + try { + return new ArrayList(storages.values()); + } finally { + engineLock.readLock().unlock(); + } + } + + /** + * @deprecated This method is not thread safe please use {@link #scheduleTask(java.util.TimerTask, long, long)} instead. + */ + @Deprecated + public Timer getTimer() { + return timer; + } + + public void removeShutdownHook() { + if (shutdownHook != null) { + shutdownHook.cancel(); + shutdownHook = null; + } + } + + public OSignalHandler getSignalHandler() { + return signalHandler; + } + + public void removeSignalHandler() { + if (signalHandler != null) { + signalHandler.cancel(); + signalHandler = null; + } + } + + public boolean isSelfManagedShutdown() { + return shutdownHook != null; + } + + public Iterator getDbLifecycleListeners() { + return new HashSet(dbLifecycleListeners.keySet()).iterator(); + } + + public void addDbLifecycleListener(final ODatabaseLifecycleListener iListener) { + final Map tmp = new LinkedHashMap( + dbLifecycleListeners); + if (iListener.getPriority() == null) + throw new IllegalArgumentException("Priority of DatabaseLifecycleListener '" + iListener + "' cannot be null"); + + tmp.put(iListener, iListener.getPriority()); + dbLifecycleListeners.clear(); + for (ODatabaseLifecycleListener.PRIORITY p : ODatabaseLifecycleListener.PRIORITY.values()) { + for (Map.Entry e : tmp.entrySet()) { + if (e.getValue() == p) + dbLifecycleListeners.put(e.getKey(), e.getValue()); + } + } + } + + public void removeDbLifecycleListener(final ODatabaseLifecycleListener iListener) { + dbLifecycleListeners.remove(iListener); + } + + public ThreadGroup getThreadGroup() { + return threadGroup; + } + + public ODatabaseThreadLocalFactory getDatabaseThreadFactory() { + return databaseThreadFactory; + } + + public ORecordFactoryManager getRecordFactoryManager() { + return recordFactoryManager; + } + + public void setRecordFactoryManager(final ORecordFactoryManager iRecordFactoryManager) { + recordFactoryManager = iRecordFactoryManager; + } + + public OProfiler getProfiler() { + return profiler; + } + + public void setProfiler(final OProfiler iProfiler) { + profiler = iProfiler; + } + + public OSecuritySystem getSecurity() { + return this.security; + } + + public void setSecurity(final OSecuritySystem security) { + this.security = security; + } + + public void registerThreadDatabaseFactory(final ODatabaseThreadLocalFactory iDatabaseFactory) { + databaseThreadFactory = iDatabaseFactory; + } + + public OScriptManager getScriptManager() { + return scriptManager; + } + + @Override + public void registerListener(OOrientListener listener) { + if (listener instanceof OOrientStartupListener) + registerOrientStartupListener((OOrientStartupListener) listener); + + super.registerListener(listener); + } + + @Override + public void unregisterListener(OOrientListener listener) { + if (listener instanceof OOrientStartupListener) + unregisterOrientStartupListener((OOrientStartupListener) listener); + + super.unregisterListener(listener); + } + + public void registerOrientStartupListener(OOrientStartupListener listener) { + startupListeners.add(listener); + } + + public void registerWeakOrientStartupListener(OOrientStartupListener listener) { + purgeWeakStartupListeners(); + weakStartupListeners.add(new WeakHashSetValueHolder(listener, removedStartupListenersQueue)); + } + + public void unregisterOrientStartupListener(OOrientStartupListener listener) { + startupListeners.remove(listener); + } + + public void unregisterWeakOrientStartupListener(OOrientStartupListener listener) { + purgeWeakStartupListeners(); + weakStartupListeners.remove(new WeakHashSetValueHolder(listener, null)); + } + + public void registerWeakOrientShutdownListener(OOrientShutdownListener listener) { + purgeWeakShutdownListeners(); + weakShutdownListeners.add(new WeakHashSetValueHolder(listener, removedShutdownListenersQueue)); + } + + public void unregisterWeakOrientShutdownListener(OOrientShutdownListener listener) { + purgeWeakShutdownListeners(); + weakShutdownListeners.remove(new WeakHashSetValueHolder(listener, null)); + } + + @Override + public void resetListeners() { + super.resetListeners(); + + weakShutdownListeners.clear(); + + startupListeners.clear(); + weakStartupListeners.clear(); + } + + public OLocalRecordCacheFactory getLocalRecordCache() { + return localRecordCache; + } + + private void purgeWeakStartupListeners() { + synchronized (removedStartupListenersQueue) { + WeakHashSetValueHolder ref = (WeakHashSetValueHolder) removedStartupListenersQueue + .poll(); + while (ref != null) { + weakStartupListeners.remove(ref); + ref = (WeakHashSetValueHolder) removedStartupListenersQueue.poll(); + } + + } + } + + private void purgeWeakShutdownListeners() { + synchronized (removedShutdownListenersQueue) { + WeakHashSetValueHolder ref = (WeakHashSetValueHolder) removedShutdownListenersQueue + .poll(); + while (ref != null) { + weakShutdownListeners.remove(ref); + ref = (WeakHashSetValueHolder) removedShutdownListenersQueue.poll(); + } + + } + } + + private boolean startEngine(OEngine engine) { + final String name = engine.getName(); + + try { + engine.startup(); + return true; + } catch (Exception e) { + OLogManager.instance().error(this, "Error during initialization of engine '%s', engine will be removed", e, name); + + try { + engine.shutdown(); + } catch (Exception se) { + OLogManager.instance().error(this, "Error during engine shutdown", se); + } + + engines.remove(name); + } + + return false; + } + + /** + * Shutdown thread group which is used in methods {@link #submit(Callable)} and {@link #submit(Runnable)}. + */ + public class OShutdownWorkersHandler implements OShutdownHandler { + @Override + public int getPriority() { + return SHUTDOWN_WORKERS_PRIORITY; + } + + @Override + public void shutdown() throws Exception { + workers.shutdown(); + try { + workers.awaitTermination(2, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + /** + * Closes all storages and shutdown all engines. + */ + public class OShutdownEnginesHandler implements OShutdownHandler { + @Override + public int getPriority() { + return SHUTDOWN_ENGINES_PRIORITY; + } + + @Override + public void shutdown() throws Exception { + shutdownAllStorages(); + + // SHUTDOWN ENGINES + for (OEngine engine : engines.values()) + if (engine.isRunning()) + engine.shutdown(); + engines.clear(); + + OByteBufferPool.instance().verifyState(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + /** + * Interrupts all threads in OrientDB thread group and stops timer is used in methods {@link #scheduleTask(TimerTask, Date, long)} + * and {@link #scheduleTask(TimerTask, long, long)}. + */ + private class OShutdownPendingThreadsHandler implements OShutdownHandler { + @Override + public int getPriority() { + return SHUTDOWN_PENDING_THREADS_PRIORITY; + } + + @Override + public void shutdown() throws Exception { + if (threadGroup != null) + // STOP ALL THE PENDING THREADS + threadGroup.interrupt(); + + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + /** + * Shutdown OrientDB profiler. + */ + private class OShutdownProfilerHandler implements OShutdownHandler { + @Override + public int getPriority() { + return SHUTDOWN_PROFILER_PRIORITY; + } + + @Override + public void shutdown() throws Exception { + // NOTE: DON'T REMOVE PROFILER TO AVOID NPE AROUND THE CODE IF ANY THREADS IS STILL WORKING + profiler.shutdown(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + /** + * Calls all shutdown listeners. + */ + private class OShutdownCallListenersHandler implements OShutdownHandler { + @Override + public int getPriority() { + return SHUTDOWN_CALL_LISTENERS; + } + + @Override + public void shutdown() throws Exception { + purgeWeakShutdownListeners(); + for (final WeakHashSetValueHolder wl : weakShutdownListeners) + try { + if (wl != null) { + final OOrientShutdownListener l = wl.get(); + if (l != null) { + l.onShutdown(); + } + } + + } catch (Exception e) { + OLogManager.instance().error(this, "Error during orient shutdown", e); + } + + // CALL THE SHUTDOWN ON ALL THE LISTENERS + for (OOrientListener l : browseListeners()) { + if (l != null) + try { + l.onShutdown(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during orient shutdown", e); + } + + } + + System.gc(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + public boolean isRunningDistributed() { + return runningDistributed; + } + + public void setRunningDistributed(final boolean runningDistributed) { + this.runningDistributed = runningDistributed; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/OrientShutdownHook.java b/core/src/main/java/com/orientechnologies/orient/core/OrientShutdownHook.java new file mode 100644 index 00000000000..c9319e1bc88 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/OrientShutdownHook.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core; + +import com.orientechnologies.common.log.OLogManager; + +public class OrientShutdownHook extends Thread { + protected OrientShutdownHook() { + try { + Runtime.getRuntime().addShutdownHook(this); + } catch (IllegalStateException e) + { + // we may be asked to initialize the runtime and install the hook from another shutdown hook during the shutdown + } + } + + /** + * Shutdown Orient engine. + */ + @Override + public void run() { + try { + Orient.instance().shutdown(); + } finally { + OLogManager.instance().shutdown(); + } + } + + public void cancel() { + try { + Runtime.getRuntime().removeShutdownHook(this); + } catch (IllegalStateException e) { + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OAccess.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAccess.java new file mode 100644 index 00000000000..1560784d69a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAccess.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells the way OrientDB should bind the field. By default OrientDB searches for getter and setter. If they are not present, then + * the field is accessed directly. Using this annotation, instead, forces the field level access. Use this if you want by-pass + * getter and setter methods. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Deprecated +public @interface OAccess { + enum OAccessType { + FIELD, PROPERTY + } + + OAccessType value() default OAccessType.PROPERTY; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterDeserialization.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterDeserialization.java new file mode 100644 index 00000000000..83281bb9596 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterDeserialization.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +/** + * Tells to OrientDB to call the method AFTER the record is read and unmarshalled from database. + * Applies only to the entity Objects reachable by the OrientDB engine after have registered them. + */ +public @interface OAfterDeserialization { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterSerialization.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterSerialization.java new file mode 100644 index 00000000000..8d72ff23d2b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OAfterSerialization.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +/** + * Tells to OrientDB to call the method AFTER the record is marshalled and written to the database. + * Applies only to the entity Objects reachable by the OrientDB engine after have registered them. + */ +public @interface OAfterSerialization { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeDeserialization.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeDeserialization.java new file mode 100644 index 00000000000..c8a8e65da6e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeDeserialization.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +/** + * Tells to OrientDB to call the method BEFORE the record is read and unmarshalled from database. + * Applies only to the entity Objects reachable by the OrientDB engine after have registered them. + */ +public @interface OBeforeDeserialization { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeSerialization.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeSerialization.java new file mode 100644 index 00000000000..85bad0ef961 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OBeforeSerialization.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +/** + * Tells to OrientDB to call the method BEFORE the record is marshalled and written to the database. + * Applies only to the entity Objects reachable by the OrientDB engine after have registered them. + */ +public @interface OBeforeSerialization { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/ODocumentInstance.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/ODocumentInstance.java new file mode 100644 index 00000000000..2a42f638ce1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/ODocumentInstance.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells that the field contains the embedded document bound to the POJO. Default is FALSE. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ODocumentInstance { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OId.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OId.java new file mode 100644 index 00000000000..9d4b1e04f1d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OId.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells that the field contains the RecordID. This is needed when you work with detached object graph. OrientDb will use this field + * to know the Document bound across different database instances. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface OId { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/annotation/OVersion.java b/core/src/main/java/com/orientechnologies/orient/core/annotation/OVersion.java new file mode 100644 index 00000000000..e150dc53dbe --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/annotation/OVersion.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells that the field contains the Document version. This is needed when you work with detached object graph and transactions. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface OVersion { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractMapCache.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractMapCache.java new file mode 100755 index 00000000000..6f5186cb12f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractMapCache.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.id.ORID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public abstract class OAbstractMapCache> implements ORecordCache { + protected T cache; + + private boolean enabled = true; + + public OAbstractMapCache(T cache) { + this.cache = cache; + } + + @Override + public void startup() { + } + + @Override + public void shutdown() { + cache.clear(); + } + + @Override + public void clear() { + cache.clear(); + } + + @Override + public int size() { + return cache.size(); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean disable() { + return enabled = false; + } + + @Override + public boolean enable() { + return enabled = true; + } + + @Override + public Collection keys() { + return new ArrayList(cache.keySet()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractRecordCache.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractRecordCache.java new file mode 100755 index 00000000000..9fff921dddd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OAbstractRecordCache.java @@ -0,0 +1,159 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue; +import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +import java.util.HashSet; +import java.util.Set; + +/** + * Cache of documents. Delegates real work on storing to {@link ORecordCache} implementation passed at creation time leaving only DB + * specific functionality + * + * @author Luca Garulli + */ +public abstract class OAbstractRecordCache { + protected ORecordCache underlying; + protected String profilerPrefix = "noname"; + protected String profilerMetadataPrefix = "noname"; + protected int excludedCluster = -1; + + /** + * Create cache backed by given implementation + * + * @param impl + * actual implementation of cache + */ + public OAbstractRecordCache(final ORecordCache impl) { + underlying = impl; + } + + + /** + * Tell whether cache is enabled + * + * @return {@code true} if cache enabled at call time, otherwise - {@code false} + */ + public boolean isEnabled() { + return underlying.isEnabled(); + } + + /** + * Switch cache state between enabled and disabled + * + * @param enable + * pass {@code true} to enable, otherwise - {@code false} + */ + public void setEnable(final boolean enable) { + if (enable) + underlying.enable(); + else + underlying.disable(); + } + + /** + * Remove record with specified identifier + * + * @param rid + * unique identifier of record + * @return record stored in cache if any, otherwise - {@code null} + */ + public ORecord freeRecord(final ORID rid) { + return underlying.remove(rid); + } + + /** + * Remove all records belonging to specified cluster + * + * @param cid + * identifier of cluster + */ + public void freeCluster(final int cid) { + final Set toRemove = new HashSet(underlying.size() / 2); + + final Set keys = new HashSet(underlying.keys()); + for (final ORID id : keys) + if (id.getClusterId() == cid) + toRemove.add(id); + + for (final ORID ridToRemove : toRemove) + underlying.remove(ridToRemove); + } + + /** + * Remove record entry + * + * @param rid + * unique record identifier + */ + public void deleteRecord(final ORID rid) { + underlying.remove(rid); + } + + /** + * Clear the entire cache by removing all the entries + */ + public void clear() { + underlying.clear(); + } + + /** + * Total number of cached entries + * + * @return non-negative integer + */ + public int getSize() { + return underlying.size(); + } + + + /** + * All operations running at cache initialization stage + */ + public void startup() { + underlying.startup(); + + Orient.instance().getProfiler() + .registerHookValue(profilerPrefix + "current", "Number of entries in cache", METRIC_TYPE.SIZE, new OProfilerHookValue() { + public Object getValue() { + return getSize(); + } + }, profilerMetadataPrefix + "current"); + + } + + /** + * All operations running at cache destruction stage + */ + public void shutdown() { + underlying.shutdown(); + + if (Orient.instance().getProfiler() != null) { + Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "enabled"); + Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "current"); + Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "max"); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OCacheLevelTwoLocator.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OCacheLevelTwoLocator.java new file mode 100755 index 00000000000..690ed0dda2d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OCacheLevelTwoLocator.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +/** + * @author Andrey Lomakin + * @since 05.07.13 + */ +public interface OCacheLevelTwoLocator { + public ORecordCache primaryCache(final String storageName); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCache.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCache.java new file mode 100644 index 00000000000..3f8f8579a66 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCache.java @@ -0,0 +1,102 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; + +import java.util.Set; + +/** + * Generic query cache interface. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + */ +public interface OCommandCache { + /** + * All operations running at cache initialization stage. + */ + void startup(); + + /** + * All operations running at cache destruction stage. + */ + void shutdown(); + + /** + * Tells whether cache is enabled. + * + * @return {@code true} if cache enabled at call time, otherwise - {@code false} + */ + boolean isEnabled(); + + /** + * Enables cache. + */ + OCommandCache enable(); + + /** + * Disables cache. None of query methods will cause effect on cache in disabled state. Only cache info methods available at that + * state. + */ + OCommandCache disable(); + + /** + * Looks up for query result in cache. + */ + Object get(OSecurityUser iUser, String queryText, int iLimit); + + /** + * Pushes record to cache. Identifier of record used as access key + */ + void put(OSecurityUser iUser, String queryText, Object iResult, int iLimit, Set iInvolvedClusters, long iExecutionTime); + + /** + * Removes result of query. + **/ + void remove(OSecurityUser iUser, String queryText, int iLimit); + + /** + * Remove all results from the cache. + */ + OCommandCache clear(); + + /** + * Total number of stored queries. + * + * @return non-negative number + */ + int size(); + + /** + * Invalidates results of given cluster. + * + * @param iCluster + * Cluster name + */ + void invalidateResultsOfCluster(final String iCluster); + + int getMaxResultsetSize(); + + STRATEGY getEvictStrategy(); + + public enum STRATEGY { + INVALIDATE_ALL, PER_CLUSTER + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheHook.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheHook.java new file mode 100644 index 00000000000..e577fa6dccf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheHook.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.hook.ORecordHookAbstract; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Hook that takes care to invalidate query cache as soon any change happen on database. + * + * @author Luca Garulli + */ +public class OCommandCacheHook extends ORecordHookAbstract implements ORecordHook.Scoped { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + private final OCommandCache cmdCache; + private final ODatabaseDocument database; + + public OCommandCacheHook(final ODatabaseDocument iDatabase) { + database = iDatabase; + cmdCache = iDatabase.getMetadata().getCommandCache().isEnabled() ? iDatabase.getMetadata().getCommandCache() : null; + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + @Override + public void onRecordAfterCreate(final ORecord iRecord) { + if (cmdCache == null) + return; + + invalidateCache(iRecord); + } + + @Override + public void onRecordAfterUpdate(final ORecord iRecord) { + if (cmdCache == null) + return; + + invalidateCache(iRecord); + } + + @Override + public void onRecordAfterDelete(final ORecord iRecord) { + if (cmdCache == null) + return; + + invalidateCache(iRecord); + } + + protected void invalidateCache(final ORecord iRecord) { + if (cmdCache.getEvictStrategy() == OCommandCacheSoftRefs.STRATEGY.PER_CLUSTER) + cmdCache.invalidateResultsOfCluster(database.getClusterNameById(iRecord.getIdentity().getClusterId())); + else + cmdCache.invalidateResultsOfCluster(null); + } + + @Override + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheSoftRefs.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheSoftRefs.java new file mode 100755 index 00000000000..bf5c8d1446f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OCommandCacheSoftRefs.java @@ -0,0 +1,431 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OResultSet; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Command cache implementation that uses Soft references to avoid overloading Java Heap. + * + * @author Luca Garulli + */ +public class OCommandCacheSoftRefs implements OCommandCache { + + private String CONFIG_FILE = "command-cache.json"; + + ODocument configuration; + + public static class OCachedResult { + Object result; + Set involvedClusters; + + public OCachedResult(final Object result, final Set involvedClusters) { + this.involvedClusters = involvedClusters; + this.result = result; + } + + protected void clear() { + result = null; + involvedClusters = null; + } + + public Object getResult() { + return result; + } + } + + private class OCommandCacheImplRefs extends OSoftRefsHashMap { + } + + private final String databaseName; + private Set clusters = new HashSet(); + private volatile boolean enable = OGlobalConfiguration.COMMAND_CACHE_ENABLED.getValueAsBoolean(); + private OCommandCacheImplRefs cache = new OCommandCacheImplRefs(); + private int minExecutionTime = OGlobalConfiguration.COMMAND_CACHE_MIN_EXECUTION_TIME.getValueAsInteger(); + private int maxResultsetSize = OGlobalConfiguration.COMMAND_CACHE_MAX_RESULSET_SIZE.getValueAsInteger(); + + private STRATEGY evictStrategy = STRATEGY + .valueOf(OGlobalConfiguration.COMMAND_CACHE_EVICT_STRATEGY.getValueAsString()); + + public OCommandCacheSoftRefs(final String iDatabaseName) { + databaseName = iDatabaseName; + + initCache(); + + } + + private void initCache() { + configuration = new ODocument(); + configuration.field("enabled", enable); + configuration.field("evictStrategy", evictStrategy.toString()); + configuration.field("minExecutionTime", minExecutionTime); + configuration.field("maxResultsetSize", maxResultsetSize); + try { + ODocument diskConfig = loadConfiguration(); + if (diskConfig != null) { + configuration = diskConfig; + configure(); + } else { + updateCfgOnDisk(); + } + } catch (Exception e) { + throw OException.wrapException(new OConfigurationException( + "Cannot change Command Cache Cache configuration file '" + CONFIG_FILE + "'. Command Cache will use default settings"), + e); + } + + } + + public void changeConfig(ODocument cfg) { + + synchronized (configuration) { + ODocument oldConfig = configuration; + configuration = cfg; + configure(); + try { + updateCfgOnDisk(); + } catch (IOException e) { + configuration = oldConfig; + configure(); + throw OException.wrapException(new OConfigurationException( + "Cannot change Command Cache Cache configuration file '" + CONFIG_FILE + "'. Command Cache will use default settings"), + e); + } + } + } + + protected void configure() { + + enable = configuration.field("enabled"); + String evict = configuration.field("evictStrategy"); + evictStrategy = STRATEGY.valueOf(evict); + minExecutionTime = configuration.field("minExecutionTime"); + maxResultsetSize = configuration.field("maxResultsetSize"); + } + + private boolean updateCfgOnDisk() throws IOException { + File f = getConfigFile(); + if (f != null) { + OLogManager.instance().debug(this, "Saving Command Cache config for db: %s", databaseName); + OIOUtils.writeFile(f, configuration.toJSON("prettyPrint")); + return true; + } + return false; + } + + private ODocument loadConfiguration() { + try { + final File f = getConfigFile(); + if (f != null && f.exists()) { + final String configurationContent = OIOUtils.readFileAsString(f); + return new ODocument().fromJSON(configurationContent); + } + } catch (Exception e) { + throw OException.wrapException( + new OConfigurationException( + "Cannot load Command Cache Cache configuration file '" + CONFIG_FILE + "'. Command Cache will use default settings"), + e); + } + return null; + } + + private File getConfigFile() { + OStorage storage = Orient.instance().getStorage(databaseName); + + if (storage instanceof OLocalPaginatedStorage) { + return new File(((OLocalPaginatedStorage) storage).getStoragePath() + File.separator + CONFIG_FILE); + } + + return null; + } + + @Override + public void startup() { + } + + @Override + public void shutdown() { + clear(); + deleteFileIfExists(); + } + + protected void deleteFileIfExists() { + File f = getConfigFile(); + if (f != null) { + OLogManager.instance().debug(this, "Removing Command Cache config for db: %s", databaseName); + f.delete(); + } + } + + @Override + public boolean isEnabled() { + return enable; + } + + @Override + public OCommandCacheSoftRefs enable() { + enable = true; + + configuration.field("enabled", true); + try { + updateCfgOnDisk(); + } catch (IOException e) { + throw OException.wrapException( + new OConfigurationException("Cannot write Command Cache Cache configuration to file '" + CONFIG_FILE + "'"), e); + } + return this; + } + + @Override + public OCommandCacheSoftRefs disable() { + enable = false; + synchronized (this) { + clusters.clear(); + cache.clear(); + } + configuration.field("enabled", true); + + try { + updateCfgOnDisk(); + } catch (IOException e) { + throw OException.wrapException( + new OConfigurationException("Cannot write Command Cache Cache configuration to file '" + CONFIG_FILE + "'"), e); + } + return this; + } + + @Override + public Object get(final OSecurityUser iUser, final String queryText, final int iLimit) { + if (!enable) + return null; + + OCachedResult result; + + synchronized (this) { + final String key = getKey(iUser, queryText, iLimit); + + result = cache.get(key); + + if (result != null) { + // SERIALIZE ALL THE RECORDS IN LOCK TO AVOID CONCURRENT ACCESS. ONCE SERIALIZED CAN ARE THREAD-SAFE + int resultsetSize = 1; + + if (result.result instanceof ORecord) + ((ORecord) result.result).toStream(); + else if (OMultiValue.isMultiValue(result.result)) { + resultsetSize = OMultiValue.getSize(result.result); + for (Object rc : OMultiValue.getMultiValueIterable(result.result)) { + if (rc != null && rc instanceof ORecord) { + ((ORecord) rc).toStream(); + } + } + } + + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Reused cached resultset size=%d", resultsetSize); + } + } + + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + // UPDATE PROFILER + if (result != null) { + profiler.updateCounter(profiler.getDatabaseMetric(databaseName, "queryCache.hit"), "Results returned by Query Cache", +1); + + } else { + profiler.updateCounter(profiler.getDatabaseMetric(databaseName, "queryCache.miss"), "Results not returned by Query Cache", + +1); + } + } + + return result != null ? result.result : null; + } + + @Override + public void put(final OSecurityUser iUser, final String queryText, final Object iResult, final int iLimit, + Set iInvolvedClusters, final long iExecutionTime) { + if (queryText == null || iResult == null) + // SKIP IT + return; + + if (!enable) + return; + + if (iExecutionTime < minExecutionTime) + // TOO FAST: AVOIDING CACHING IT + return; + + int resultsetSize = 1; + if (iResult instanceof OResultSet) { + resultsetSize = ((OResultSet) iResult).size(); + + if (resultsetSize > maxResultsetSize) + // TOO BIG RESULTSET, SKIP IT + return; + } + + if (evictStrategy != STRATEGY.PER_CLUSTER) + iInvolvedClusters = null; + + synchronized (this) { + final String key = getKey(iUser, queryText, iLimit); + final OCachedResult value = new OCachedResult(iResult, iInvolvedClusters); + + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Storing resultset in cache size=%d", resultsetSize); + + cache.put(key, value); + + if (iInvolvedClusters != null) + clusters.addAll(iInvolvedClusters); + } + } + + @Override + public void remove(final OSecurityUser iUser, final String queryText, final int iLimit) { + if (!enable) + return; + + synchronized (this) { + final String key = getKey(iUser, queryText, iLimit); + cache.remove(key); + } + } + + @Override + public OCommandCacheSoftRefs clear() { + synchronized (this) { + cache = new OCommandCacheImplRefs(); + clusters.clear(); + } + return this; + } + + @Override + public int size() { + synchronized (this) { + return cache.size(); + } + } + + @Override + public void invalidateResultsOfCluster(final String iCluster) { + if (!enable) + return; + + synchronized (this) { + if (cache.size() == 0) + return; + + if (evictStrategy == STRATEGY.INVALIDATE_ALL) { + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Invalidate all cached results (%d)", size()); + + clear(); + return; + } + + if (!clusters.remove(iCluster)) { + // NOT CONTAINED, AVOID COSTLY BROWSING OF RESULTS + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "No results found for '%s'", iCluster); + return; + } + + int evicted = 0; + for (Iterator> it = cache.entrySet().iterator(); it.hasNext();) { + final OCachedResult cached = it.next().getValue(); + if (cached != null) { + if (cached.involvedClusters == null || cached.involvedClusters.isEmpty() || cached.involvedClusters.contains(iCluster)) { + cached.clear(); + it.remove(); + } + } + } + + if (evicted > 0 && OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Invalidate %d cached results associated to the cluster '%s'", evicted, iCluster); + } + } + + public int getMinExecutionTime() { + return minExecutionTime; + } + + public OCommandCacheSoftRefs setMinExecutionTime(final int minExecutionTime) { + this.minExecutionTime = minExecutionTime; + return this; + } + + @Override + public int getMaxResultsetSize() { + return maxResultsetSize; + } + + public OCommandCacheSoftRefs setMaxResultsetSize(final int maxResultsetSize) { + this.maxResultsetSize = maxResultsetSize; + return this; + } + + @Override + public STRATEGY getEvictStrategy() { + return evictStrategy; + } + + public OCommandCacheSoftRefs setEvictStrategy(final STRATEGY evictStrategy) { + this.evictStrategy = evictStrategy; + return this; + } + + protected String getKey(final OSecurityUser iUser, final String queryText, final int iLimit) { + if (iUser == null) + return "." + queryText + "." + iLimit; + + return iUser + "." + queryText + "." + iLimit; + } + + public Set> entrySet() { + synchronized (this) { + return cache.entrySet(); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCache.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCache.java new file mode 100755 index 00000000000..fc579b39526 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCache.java @@ -0,0 +1,121 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordVersionHelper; + +/** + * Local cache. it's one to one with record database instances. It is needed to avoid cases when several instances of the same + * record will be loaded by user from the same database. + * + * @author Luca Garulli + */ +public class OLocalRecordCache extends OAbstractRecordCache { + private String CACHE_HIT; + private String CACHE_MISS; + + public OLocalRecordCache() { + super(Orient.instance().getLocalRecordCache().newInstance(OGlobalConfiguration.CACHE_LOCAL_IMPL.getValueAsString())); + } + + @Override + public void startup() { + ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + + profilerPrefix = "db." + db.getName() + ".cache.level1."; + profilerMetadataPrefix = "db.*.cache.level1."; + + CACHE_HIT = profilerPrefix + "cache.found"; + CACHE_MISS = profilerPrefix + "cache.notFound"; + + super.startup(); + } + + /** + * Pushes record to cache. Identifier of record used as access key + * + * @param record + * record that should be cached + */ + public void updateRecord(final ORecord record) { + if (record.getIdentity().getClusterId() != excludedCluster && record.getIdentity().isValid() && !record.isDirty() + && !ORecordVersionHelper.isTombstone(record.getVersion())) { + if (underlying.get(record.getIdentity()) != record) + underlying.put(record); + } + } + + /** + * Looks up for record in cache by it's identifier. Optionally look up in secondary cache and update primary with found record + * + * @param rid + * unique identifier of record + * @return record stored in cache if any, otherwise - {@code null} + */ + public ORecord findRecord(final ORID rid) { + ORecord record; + record = underlying.get(rid); + + if (record != null) + Orient.instance().getProfiler().updateCounter(CACHE_HIT, "Record found in Level1 Cache", 1L, "db.*.cache.level1.cache.found"); + else + Orient.instance().getProfiler().updateCounter(CACHE_MISS, "Record not found in Level1 Cache", 1L, + "db.*.cache.level1.cache.notFound"); + + return record; + } + + /** + * Removes record with specified identifier from both primary and secondary caches + * + * @param rid + * unique identifier of record + */ + public void deleteRecord(final ORID rid) { + super.deleteRecord(rid); + } + + public void shutdown() { + super.shutdown(); + } + + @Override + public void clear() { + super.clear(); + } + + /** + * Invalidates the cache emptying all the records. + */ + public void invalidate() { + underlying.clear(); + } + + @Override + public String toString() { + return "DB level cache records = " + getSize(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactory.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactory.java new file mode 100755 index 00000000000..fe58cf9d92b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactory.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +/** + * Factory interface of local record cache. + * + * @author Luca Garulli + */ +public interface OLocalRecordCacheFactory { + ORecordCache newInstance(final String iName); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactoryImpl.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactoryImpl.java new file mode 100755 index 00000000000..6d5100008c4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OLocalRecordCacheFactoryImpl.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.common.factory.OConfigurableStatefulFactory; + +/** + * Factory implementation of local record cache. + * + * @author Luca Garulli + */ +public class OLocalRecordCacheFactoryImpl extends OConfigurableStatefulFactory implements OLocalRecordCacheFactory { + public OLocalRecordCacheFactoryImpl() { + register(ORecordCacheWeakRefs.class.getName(), ORecordCacheWeakRefs.class); + register(ORecordCacheSoftRefs.class.getName(), ORecordCacheSoftRefs.class); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCache.java b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCache.java new file mode 100644 index 00000000000..21ed86d720b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCache.java @@ -0,0 +1,125 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +import java.util.Collection; + +/** + * Generic cache interface that should be implemented in order to plug-in custom cache. Also implementing class has to have public + * one-arg constructor to set cache limit. For example, next class can be safely used as plug-in cache: + * + *
      + *   public class CustomCache implements OCache {
      + *     public CustomCache(int initialLimit) {
      + *       // some actions to do basic initialization of cache instance
      + *       ...
      + *     }
      + * 
      + *     //implementation of interface
      + *     ...
      + *   }
      + * 
      + * + * As reference implementation used {@link ORecordCacheWeakRefs} + * + * @author Maxim Fedorov + */ +public interface ORecordCache { + /** + * All operations running at cache initialization stage + */ + void startup(); + + /** + * All operations running at cache destruction stage + */ + void shutdown(); + + /** + * Tell whether cache is enabled + * + * @return {@code true} if cache enabled at call time, otherwise - {@code false} + */ + boolean isEnabled(); + + /** + * Enable cache + * + * @return {@code true} - if enabled, {@code false} - otherwise (already enabled) + */ + boolean enable(); + + /** + * Disable cache. None of record management methods will cause effect on cache in disabled state. Only cache info methods + * available at that state. + * + * @return {@code true} - if disabled, {@code false} - otherwise (already disabled) + */ + boolean disable(); + + /** + * Look up for record in cache by it's identifier + * + * @param id + * unique identifier of record + * @return record stored in cache if any, otherwise - {@code null} + */ + ORecord get(ORID id); + + /** + * Push record to cache. Identifier of record used as access key + * + * @param record + * record that should be cached + * @return previous version of record + */ + ORecord put(ORecord record); + + /** + * Remove record with specified identifier + * + * @param id + * unique identifier of record + * @return record stored in cache if any, otherwise - {@code null} + */ + ORecord remove(ORID id); + + /** + * Remove all records from cache + */ + void clear(); + + /** + * Total number of stored records + * + * @return non-negative number + */ + int size(); + + /** + * Keys of all stored in cache records + * + * @return keys of records + */ + Collection keys(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheSoftRefs.java b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheSoftRefs.java new file mode 100755 index 00000000000..e82125a5e75 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheSoftRefs.java @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Cache implementation that uses Soft References. + * + * @author Luca Garulli (l.garulli-at-orientdb.com) + */ +public class ORecordCacheSoftRefs extends OAbstractMapCache> implements ORecordCache { + + public ORecordCacheSoftRefs() { + super(new OSoftRefsHashMap()); + } + + @Override + public ORecord get(final ORID rid) { + if (!isEnabled()) + return null; + + return cache.get(rid); + } + + @Override + public ORecord put(final ORecord record) { + if (!isEnabled()) + return null; + return cache.put(record.getIdentity(), record); + } + + @Override + public ORecord remove(final ORID rid) { + if (!isEnabled()) + return null; + return cache.remove(rid); + } + + @Override + public void shutdown() { + clear(); + } + + @Override + public void clear() { + cache.clear(); + cache = new OSoftRefsHashMap(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheWeakRefs.java b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheWeakRefs.java new file mode 100755 index 00000000000..64cc63d51e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/ORecordCacheWeakRefs.java @@ -0,0 +1,82 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class ORecordCacheWeakRefs extends OAbstractMapCache>> implements ORecordCache { + + public ORecordCacheWeakRefs() { + super(new WeakHashMap>()); + } + + @Override + public ORecord get(final ORID rid) { + if (!isEnabled()) + return null; + + final WeakReference value; + value = cache.get(rid); + return get(value); + } + + @Override + public ORecord put(final ORecord record) { + if (!isEnabled()) + return null; + final WeakReference value; + value = cache.put(record.getIdentity(), new WeakReference(record)); + return get(value); + } + + @Override + public ORecord remove(final ORID rid) { + if (!isEnabled()) + return null; + final WeakReference value; + value = cache.remove(rid); + return get(value); + } + + private ORecord get(WeakReference value) { + if (value == null) + return null; + else + return value.get(); + } + + @Override + public void shutdown() { + cache = new WeakHashMap>(); + } + + @Override + public void clear() { + cache.clear(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/cache/OSoftRefsHashMap.java b/core/src/main/java/com/orientechnologies/orient/core/cache/OSoftRefsHashMap.java new file mode 100644 index 00000000000..23f89e50a75 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/cache/OSoftRefsHashMap.java @@ -0,0 +1,106 @@ +package com.orientechnologies.orient.core.cache; + +import com.orientechnologies.common.log.OLogManager; + +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.AbstractMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Soft References Map inspired by the code published by Dr. Heinz M. Kabutz on http://www.javaspecialists.eu/archive/Issue015.html. + */ +public class OSoftRefsHashMap extends AbstractMap implements Serializable { + private final Map> hashCodes = new ConcurrentHashMap>(); + private final Map, K> reverseLookup = new ConcurrentHashMap, K>(); + private final ReferenceQueue refQueue = new ReferenceQueue(); + + public V get(Object key) { + evictStaleEntries(); + V result = null; + final SoftReference soft_ref = hashCodes.get(key); + if (soft_ref != null) { + result = soft_ref.get(); + if (result == null) { + hashCodes.remove(key); + reverseLookup.remove(soft_ref); + } + } + return result; + } + + private void evictStaleEntries() { + int evicted = 0; + + Reference sv; + while ((sv = refQueue.poll()) != null) { + final K key = reverseLookup.remove(sv); + if (key != null) { + hashCodes.remove(key); + evicted++; + } + } + + if (evicted > 0) + OLogManager.instance().debug(this, "Evicted %d items", evicted); + } + + public V put(final K key, final V value) { + evictStaleEntries(); + final SoftReference soft_ref = new SoftReference(value, refQueue); + reverseLookup.put(soft_ref, key); + final SoftReference result = hashCodes.put(key, soft_ref); + if (result == null) + return null; + reverseLookup.remove(result); + return result.get(); + } + + public V remove(Object key) { + evictStaleEntries(); + final SoftReference result = hashCodes.remove(key); + if (result == null) + return null; + return result.get(); + } + + public void clear() { + hashCodes.clear(); + reverseLookup.clear(); + } + + public int size() { + evictStaleEntries(); + return hashCodes.size(); + } + + public Set> entrySet() { + evictStaleEntries(); + Set> result = new LinkedHashSet>(); + for (final Entry> entry : hashCodes.entrySet()) { + final V value = entry.getValue().get(); + if (value != null) { + result.add(new Entry() { + public K getKey() { + return entry.getKey(); + } + + public V getValue() { + return value; + } + + public V setValue(V v) { + entry.setValue(new SoftReference(v, refQueue)); + return value; + } + }); + } + } + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/OCaseInsensitiveCollate.java b/core/src/main/java/com/orientechnologies/orient/core/collate/OCaseInsensitiveCollate.java new file mode 100755 index 00000000000..d95d922d4cf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/OCaseInsensitiveCollate.java @@ -0,0 +1,80 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.collate; + +import com.orientechnologies.common.comparator.ODefaultComparator; + +import java.util.*; + +/** + * Case insensitive collate. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OCaseInsensitiveCollate extends ODefaultComparator implements OCollate { + public static final String NAME = "ci"; + + public String getName() { + return NAME; + } + + public Object transform(final Object obj) { + if (obj instanceof String) + return ((String) obj).toLowerCase(Locale.ENGLISH); + + if(obj instanceof Set){ + Set result = new HashSet(); + for(Object o:(Set)obj){ + result.add(transform(o)); + } + return result; + } + + if(obj instanceof List){ + List result = new ArrayList(); + for(Object o:(List)obj){ + result.add(transform(o)); + } + return result; + } + return obj; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj==null || obj.getClass() != this.getClass()) + return false; + + final OCaseInsensitiveCollate that = (OCaseInsensitiveCollate) obj; + + return getName().equals(that.getName()); + } + + @Override + public String toString() { + return "{" + getClass().getSimpleName() + " : name = " + getName() + "}"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/OCollate.java b/core/src/main/java/com/orientechnologies/orient/core/collate/OCollate.java new file mode 100755 index 00000000000..9ed825c3853 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/OCollate.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.collate; + +import java.io.Serializable; + +/** + * Specify the Collating strategy when comparison in SQL statement is required. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public interface OCollate extends Serializable { + String getName(); + + Object transform(Object obj); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/OCollateFactory.java b/core/src/main/java/com/orientechnologies/orient/core/collate/OCollateFactory.java new file mode 100755 index 00000000000..20391daeb3e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/OCollateFactory.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.collate; + +import java.util.Set; + +/** + * the Collating strategy when comparison in SQL statement is required. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCollateFactory { + + /** + * @return Set of supported collate names of this factory + */ + Set getNames(); + + /** + * Returns the requested collate + * + * @param name + */ + OCollate getCollate(String name); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollate.java b/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollate.java new file mode 100755 index 00000000000..a374e7a1912 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollate.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.collate; + +import com.orientechnologies.common.comparator.ODefaultComparator; + +/** + * Default collate, does not apply conversions. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ODefaultCollate extends ODefaultComparator implements OCollate { + public static final String NAME = "default"; + + public String getName() { + return NAME; + } + + public Object transform(final Object obj) { + return obj; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj==null || obj.getClass() != this.getClass()) + return false; + + final ODefaultCollate that = (ODefaultCollate) obj; + + return getName().equals(that.getName()); + } + + @Override + public String toString() { + return "{" + getClass().getSimpleName() + " : name = " + getName() + "}"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollateFactory.java b/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollateFactory.java new file mode 100755 index 00000000000..8023dd3c284 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/ODefaultCollateFactory.java @@ -0,0 +1,62 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.collate; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Factory to hold collating strategies to compare values in SQL statement and indexes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ODefaultCollateFactory implements OCollateFactory { + + private static final Map COLLATES = new HashMap(2); + + static { + register(new ODefaultCollate()); + register(new OCaseInsensitiveCollate()); + } + + /** + * @return Set of supported collate names of this factory + */ + @Override + public Set getNames() { + return COLLATES.keySet(); + } + + /** + * Returns the requested collate + * + * @param name + */ + @Override + public OCollate getCollate(final String name) { + return COLLATES.get(name); + } + + private static void register(final OCollate iCollate) { + COLLATES.put(iCollate.getName(), iCollate); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OBasicCommandContext.java b/core/src/main/java/com/orientechnologies/orient/core/command/OBasicCommandContext.java new file mode 100644 index 00000000000..3dfce0ce6b5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OBasicCommandContext.java @@ -0,0 +1,354 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.concur.OTimeoutException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Basic implementation of OCommandContext interface that stores variables in a map. Supports parent/child context to build a tree + * of contexts. If a variable is not found on current object the search is applied recursively on child contexts. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OBasicCommandContext implements OCommandContext { + public static final String EXECUTION_BEGUN = "EXECUTION_BEGUN"; + public static final String TIMEOUT_MS = "TIMEOUT_MS"; + public static final String TIMEOUT_STRATEGY = "TIMEOUT_STARTEGY"; + public static final String INVALID_COMPARE_COUNT = "INVALID_COMPARE_COUNT"; + + protected boolean recordMetrics = false; + protected OCommandContext parent; + protected OCommandContext child; + protected Map variables; + + protected Map inputParameters; + + // MANAGES THE TIMEOUT + private long executionStartedOn; + private long timeoutMs; + private com.orientechnologies.orient.core.command.OCommandContext.TIMEOUT_STRATEGY timeoutStrategy; + protected AtomicLong resultsProcessed = new AtomicLong(0); + protected Set uniqueResult = new HashSet(); + + public OBasicCommandContext() { + } + + public Object getVariable(String iName) { + return getVariable(iName, null); + } + + public Object getVariable(String iName, final Object iDefault) { + if (iName == null) + return iDefault; + + Object result = null; + + if (iName.startsWith("$")) + iName = iName.substring(1); + + int pos = OStringSerializerHelper.getLowerIndexOf(iName, 0, ".", "["); + + String firstPart; + String lastPart; + if (pos > -1) { + firstPart = iName.substring(0, pos); + if (iName.charAt(pos) == '.') + pos++; + lastPart = iName.substring(pos); + if (firstPart.equalsIgnoreCase("PARENT") && parent != null) { + // UP TO THE PARENT + if (lastPart.startsWith("$")) + result = parent.getVariable(lastPart.substring(1)); + else + result = ODocumentHelper.getFieldValue(parent, lastPart); + + return result != null ? result : iDefault; + + } else if (firstPart.equalsIgnoreCase("ROOT")) { + OCommandContext p = this; + while (p.getParent() != null) + p = p.getParent(); + + if (lastPart.startsWith("$")) + result = p.getVariable(lastPart.substring(1)); + else + result = ODocumentHelper.getFieldValue(p, lastPart, this); + + return result != null ? result : iDefault; + } + } else { + firstPart = iName; + lastPart = null; + } + + if (firstPart.equalsIgnoreCase("CONTEXT")) + result = getVariables(); + else if (firstPart.equalsIgnoreCase("PARENT")) + result = parent; + else if (firstPart.equalsIgnoreCase("ROOT")) { + OCommandContext p = this; + while (p.getParent() != null) + p = p.getParent(); + result = p; + } else { + if (variables != null && variables.containsKey(firstPart)) + result = variables.get(firstPart); + else { + if (child != null) + result = child.getVariable(firstPart); + else + result = getVariableFromParentHierarchy(firstPart); + } + } + + if (pos > -1) + result = ODocumentHelper.getFieldValue(result, lastPart, this); + + return result != null ? result : iDefault; + } + + protected Object getVariableFromParentHierarchy(String varName) { + if (this.variables != null && variables.containsKey(varName)) { + return variables.get(varName); + } + if (parent!=null && parent instanceof OBasicCommandContext) { + return ((OBasicCommandContext) parent).getVariableFromParentHierarchy(varName); + } + return null; + } + + public OCommandContext setVariable(String iName, final Object iValue) { + if (iName == null) + return null; + + if (iName.startsWith("$")) + iName = iName.substring(1); + + init(); + + int pos = OStringSerializerHelper.getHigherIndexOf(iName, 0, ".", "["); + if (pos > -1) { + Object nested = getVariable(iName.substring(0, pos)); + if (nested != null && nested instanceof OCommandContext) + ((OCommandContext) nested).setVariable(iName.substring(pos + 1), iValue); + } else + variables.put(iName, iValue); + return this; + } + + @Override + public OCommandContext incrementVariable(String iName) { + if (iName != null) { + if (iName.startsWith("$")) + iName = iName.substring(1); + + init(); + + int pos = OStringSerializerHelper.getHigherIndexOf(iName, 0, ".", "["); + if (pos > -1) { + Object nested = getVariable(iName.substring(0, pos)); + if (nested != null && nested instanceof OCommandContext) + ((OCommandContext) nested).incrementVariable(iName.substring(pos + 1)); + } else { + final Object v = variables.get(iName); + if (v == null) + variables.put(iName, 1); + else if (v instanceof Number) + variables.put(iName, OType.increment((Number) v, 1)); + else + throw new IllegalArgumentException("Variable '" + iName + "' is not a number, but: " + v.getClass()); + } + } + return this; + } + + public long updateMetric(final String iName, final long iValue) { + if (!recordMetrics) + return -1; + + init(); + Long value = (Long) variables.get(iName); + if (value == null) + value = iValue; + else + value = new Long(value.longValue() + iValue); + variables.put(iName, value); + return value.longValue(); + } + + /** + * Returns a read-only map with all the variables. + */ + public Map getVariables() { + final HashMap map = new HashMap(); + if (child != null) + map.putAll(child.getVariables()); + + if (variables != null) + map.putAll(variables); + + return map; + } + + /** + * Set the inherited context avoiding to copy all the values every time. + * + * @return + */ + public OCommandContext setChild(final OCommandContext iContext) { + if (iContext == null) { + if (child != null) { + // REMOVE IT + child.setParent(null); + child = null; + } + + } else if (child != iContext) { + // ADD IT + child = iContext; + iContext.setParent(this); + } + return this; + } + + public OCommandContext getParent() { + return parent; + } + + public OCommandContext setParent(final OCommandContext iParentContext) { + if (parent != iParentContext) { + parent = iParentContext; + if (parent != null) + parent.setChild(this); + } + return this; + } + + @Override + public String toString() { + return getVariables().toString(); + } + + public boolean isRecordingMetrics() { + return recordMetrics; + } + + public OCommandContext setRecordingMetrics(final boolean recordMetrics) { + this.recordMetrics = recordMetrics; + return this; + } + + @Override + public void beginExecution(final long iTimeout, final TIMEOUT_STRATEGY iStrategy) { + if (iTimeout > 0) { + executionStartedOn = System.currentTimeMillis(); + timeoutMs = iTimeout; + timeoutStrategy = iStrategy; + } + } + + public boolean checkTimeout() { + if (timeoutMs > 0) { + if (System.currentTimeMillis() - executionStartedOn > timeoutMs) { + // TIMEOUT! + switch (timeoutStrategy) { + case RETURN: + return false; + case EXCEPTION: + throw new OTimeoutException("Command execution timeout exceed (" + timeoutMs + "ms)"); + } + } + } else if (parent != null) + // CHECK THE TIMER OF PARENT CONTEXT + return parent.checkTimeout(); + + return true; + } + + @Override + public OCommandContext copy() { + final OBasicCommandContext copy = new OBasicCommandContext(); + copy.init(); + + if (variables != null && !variables.isEmpty()) + copy.variables.putAll(variables); + + copy.recordMetrics = recordMetrics; + copy.parent = parent; + copy.child = child; + return copy; + } + + @Override + public void merge(final OCommandContext iContext) { + // TODO: SOME VALUES NEED TO BE MERGED + } + + private void init() { + if (variables == null) + variables = new HashMap(); + } + + public Map getInputParameters() { + if (inputParameters != null) { + return inputParameters; + } + + return parent == null ? null : parent.getInputParameters(); + } + + public void setInputParameters(Map inputParameters) { + this.inputParameters = inputParameters; + + } + + /** + * returns the number of results processed. This is intended to be used with LIMIT in SQL statements + * + * @return + */ + public AtomicLong getResultsProcessed() { + return resultsProcessed; + } + + /** + * adds an item to the unique result set + * @param o the result item to add + * @return true if the element is successfully added (it was not present yet), false otherwise (it was already present) + */ + public synchronized boolean addToUniqueResult(Object o) { + Object toAdd = o; + if(o instanceof ODocument && ((ODocument) o).getIdentity().isNew()){ + toAdd = new ODocumentEqualityWrapper((ODocument) o); + } + return this.uniqueResult.add(toAdd); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommand.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommand.java new file mode 100644 index 00000000000..e20a2b8cbea --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommand.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + + +/** + * Generic GOF command pattern implementation. + * + * @author Luca Garulli + * + * @param + */ +public interface OCommand { + /** + * Executes command. + * + * @return The result of command if any, otherwise null + */ + public Object execute(); + + public OCommandContext getContext(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandContext.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandContext.java new file mode 100644 index 00000000000..22a1d2ae1a8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandContext.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.concur.OTimeoutException; + +import java.util.Map; + +/** + * Basic interface for commands. Manages the context variables during execution. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCommandContext { + enum TIMEOUT_STRATEGY { + RETURN, EXCEPTION + } + + Object getVariable(String iName); + + Object getVariable(String iName, Object iDefaultValue); + + OCommandContext setVariable(String iName, Object iValue); + + OCommandContext incrementVariable(String getNeighbors); + + Map getVariables(); + + OCommandContext getParent(); + + OCommandContext setParent(OCommandContext iParentContext); + + OCommandContext setChild(OCommandContext context); + + /** + * Updates a counter. Used to record metrics. + * + * @param iName + * Metric's name + * @param iValue + * delta to add or subtract + * @return + */ + long updateMetric(String iName, long iValue); + + boolean isRecordingMetrics(); + + OCommandContext setRecordingMetrics(boolean recordMetrics); + + void beginExecution(long timeoutMs, TIMEOUT_STRATEGY iStrategy); + + /** + * Check if timeout is elapsed, if defined. + * + * @return false if it the timeout is elapsed and strategy is "return" + * @exception OTimeoutException + * if the strategy is "exception" (default) + */ + public boolean checkTimeout(); + + public Map getInputParameters(); + + public void setInputParameters(Map inputParameters); + + /** + * Creates a copy of execution context. + */ + OCommandContext copy(); + + /** + * Merges a context with current one. + * + * @param iContext + */ + void merge(OCommandContext iContext); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandDistributedReplicateRequest.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandDistributedReplicateRequest.java new file mode 100644 index 00000000000..5629918ab7f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandDistributedReplicateRequest.java @@ -0,0 +1,85 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +/** + * Interface to know if the command must be distributed in clustered scenario. + * + * @author Luca Garulli + */ +public interface OCommandDistributedReplicateRequest { + + enum DISTRIBUTED_EXECUTION_MODE { + LOCAL, REPLICATE + } + + enum DISTRIBUTED_RESULT_MGMT { + CHECK_FOR_EQUALS, MERGE + } + + enum QUORUM_TYPE { + NONE, READ, WRITE, ALL + } + + /** + * Returns the execution mode when distributed configuration is active: + *
        + *
      • LOCAL: executed on local node only
      • + *
      • REPLICATE: executed on all the nodes and expect the same result
      • + *
      • SHARDED: executed on all the involved nodes and merge results
      • + *
      + */ + DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode(); + + /** + * Returns how to manage the distributed result between: + *
        + *
      • CHECK_FOR_EQUALS: all results must be the same
      • + *
      • MERGE: merges results. This is typically used on sharding
      • + *
      + */ + DISTRIBUTED_RESULT_MGMT getDistributedResultManagement(); + + /** + * Returns the quorum type for the command: + *
        + *
      • NONE: no quorum
      • + *
      • READ: configured Read quorum
      • + *
      • WRITE: configured Write quorum
      • + *
      • ALL: all nodes
      • + *
      + */ + QUORUM_TYPE getQuorumType(); + + /** + * Returns the distributed timeout in milliseconds. + */ + long getDistributedTimeout(); + + /** + * Returns the undo command if any. + */ + String getUndoCommand(); + + /** + * Returns true if the command is executed on local node first and then distributed, or false if it's executed to all the servers at the same time. + */ + boolean isDistributedExecutingOnLocalNodeFirst(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutor.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutor.java new file mode 100644 index 00000000000..4a30a185732 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutor.java @@ -0,0 +1,110 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.listener.OProgressListener; + +import java.util.Map; +import java.util.Set; + +/** + * Generic GOF command pattern implementation. + * + * @author Luca Garulli + */ +public interface OCommandExecutor { + + /** + * Parse the request. Once parsed the command can be executed multiple times by using the execute() method. + * + * @param iRequest + * Command request implementation. + * + * @see #execute(Map...) + * @return + */ + RET parse(OCommandRequest iRequest); + + /** + * Execute the requested command parsed previously. + * + * @param iArgs + * Optional variable arguments to pass to the command. + * + * @see #parse(OCommandRequest) + * @return + */ + Object execute(final Map iArgs); + + /** + * Set the listener invoked while the command is executing. + * + * @param progressListener + * OProgressListener implementation + * @return + */ + RET setProgressListener(OProgressListener progressListener); + + RET setLimit(int iLimit); + + String getFetchPlan(); + + Map getParameters(); + + OCommandContext getContext(); + + void setContext(OCommandContext context); + + /** + * Returns true if the command doesn't change the database, otherwise false. + */ + boolean isIdempotent(); + + /** + * Returns the involved clusters. + */ + Set getInvolvedClusters(); + + /** + * Returns the security operation type use to check about security. + * + * @see com.orientechnologies.orient.core.metadata.security.ORole PERMISSION_* + * @return + */ + int getSecurityOperationType(); + + boolean involveSchema(); + + String getSyntax(); + + /** + * Returns true if the command must be executed on local node on distributed configuration. + */ + boolean isLocalExecution(); + + /** + * Returns true if the command results can be cached. + */ + boolean isCacheable(); + + long getDistributedTimeout(); + + Object mergeResults(Map results) throws Exception; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorAbstract.java new file mode 100644 index 00000000000..92f1b9dd172 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorAbstract.java @@ -0,0 +1,207 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OExecutionThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandInterruptedException; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; + +import java.util.*; + +/** + * Abstract implementation of Executor Command interface. + * + * @author Luca Garulli + * + */ +@SuppressWarnings("unchecked") +public abstract class OCommandExecutorAbstract extends OBaseParser implements OCommandExecutor { + protected OProgressListener progressListener; + protected int limit = -1; + protected Map parameters; + protected OCommandContext context; + + public static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public OCommandExecutorAbstract init(final OCommandRequestText iRequest) { + getDatabase().checkSecurity(ORule.ResourceGeneric.COMMAND, ORole.PERMISSION_READ); + parserText = iRequest.getText().trim(); + parserTextUpperCase = upperCase(parserText); + return this; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [text=" + parserText + "]"; + } + + public OProgressListener getProgressListener() { + return progressListener; + } + + public RET setProgressListener(final OProgressListener progressListener) { + this.progressListener = progressListener; + return (RET) this; + } + + public String getUndoCommand() { + return null; + } + + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_LONG_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + public int getLimit() { + return limit; + } + + public RET setLimit(final int iLimit) { + this.limit = iLimit; + return (RET) this; + } + + public Map getParameters() { + return parameters; + } + + @Override + public String getFetchPlan() { + return null; + } + + public OCommandContext getContext() { + if (context == null) + context = new OBasicCommandContext(); + return context; + } + + public void setContext(final OCommandContext iContext) { + context = iContext; + } + + @Override + public Set getInvolvedClusters() { + return Collections.EMPTY_SET; + } + + @Override + public int getSecurityOperationType() { + return ORole.PERMISSION_READ; + } + + public boolean involveSchema() { + return false; + } + + protected boolean checkInterruption() { + return checkInterruption(this.context); + } + + public static boolean checkInterruption(final OCommandContext iContext) { + if (OExecutionThreadLocal.isInterruptCurrentOperation()) + throw new OCommandInterruptedException("The command has been interrupted"); + + if (iContext != null && !iContext.checkTimeout()) + return false; + + return true; + } + + protected String upperCase(String text) { + StringBuilder result = new StringBuilder(text.length()); + for (char c : text.toCharArray()) { + String upper = ("" + c).toUpperCase(Locale.ENGLISH); + if (upper.length() > 1) { + result.append(c); + } else { + result.append(upper); + } + } + return result.toString(); + } + + public OCommandDistributedReplicateRequest.DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() { + return OCommandDistributedReplicateRequest.DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS; + } + + @Override + public boolean isLocalExecution() { + return false; + } + + @Override + public boolean isCacheable() { + return false; + } + + public Object mergeResults(final Map results) throws Exception { + + if (results.isEmpty()) + return null; + + Object aggregatedResult = null; + + for (Map.Entry entry : results.entrySet()) { + final String nodeName = entry.getKey(); + final Object nodeResult = entry.getValue(); + + if (nodeResult instanceof Collection) { + if (aggregatedResult == null) + aggregatedResult = new ArrayList(); + + ((List) aggregatedResult).addAll((Collection) nodeResult); + + } else if (nodeResult instanceof Exception) + + // RECEIVED EXCEPTION + throw (Exception) nodeResult; + + else if (nodeResult instanceof OIdentifiable) { + if (aggregatedResult == null) + aggregatedResult = new ArrayList(); + + ((List) aggregatedResult).add(nodeResult); + + } else if (nodeResult instanceof Number) { + if (aggregatedResult == null) + aggregatedResult = nodeResult; + else + OMultiValue.add(aggregatedResult, nodeResult); + } + } + + return aggregatedResult; + } + + public boolean isDistributedExecutingOnLocalNodeFirst(){ + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorNotFoundException.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorNotFoundException.java new file mode 100755 index 00000000000..4e87fc71729 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandExecutorNotFoundException.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +public class OCommandExecutorNotFoundException extends OCommandExecutionException { + + private static final long serialVersionUID = -7430575036316163711L; + + public OCommandExecutorNotFoundException(OCommandExecutorNotFoundException exception) { + super(exception); + } + + public OCommandExecutorNotFoundException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandManager.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandManager.java new file mode 100755 index 00000000000..d7aa260d983 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandManager.java @@ -0,0 +1,120 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.command.script.OCommandExecutorScript; +import com.orientechnologies.orient.core.command.script.OCommandScript; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.*; +import com.orientechnologies.orient.core.sql.query.OLiveQuery; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLNonBlockingQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.HashMap; +import java.util.Map; + +public class OCommandManager { + private static OCommandManager instance = new OCommandManager(); + private Map> commandRequesters = new HashMap>(); + private Map, OCallable> configCallbacks = new HashMap, OCallable>(); + private Map, Class> commandReqExecMap = new HashMap, Class>(); + + protected OCommandManager() { + registerRequester("sql", OCommandSQL.class); + registerRequester("script", OCommandScript.class); + + registerExecutor(OSQLAsynchQuery.class, OCommandExecutorSQLDelegate.class); + registerExecutor(OSQLSynchQuery.class, OCommandExecutorSQLDelegate.class); + registerExecutor(OSQLNonBlockingQuery.class, OCommandExecutorSQLDelegate.class); + registerExecutor(OLiveQuery.class, OCommandExecutorSQLLiveSelect.class); + registerExecutor(OCommandSQL.class, OCommandExecutorSQLDelegate.class); + registerExecutor(OCommandSQLResultset.class, OCommandExecutorSQLResultsetDelegate.class); + registerExecutor(OCommandScript.class, OCommandExecutorScript.class); + } + + public static OCommandManager instance() { + return instance; + } + + public OCommandManager registerRequester(final String iType, final Class iRequest) { + commandRequesters.put(iType, iRequest); + return this; + } + + public boolean existsRequester(final String iType) { + return commandRequesters.containsKey(iType); + } + + public OCommandRequest getRequester(final String iType) { + final Class reqClass = commandRequesters.get(iType); + + if (reqClass == null) + throw new IllegalArgumentException("Cannot find a command requester for type: " + iType); + + try { + return reqClass.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot create the command requester of class " + reqClass + " for type: " + iType, e); + } + } + + public OCommandManager registerExecutor(final Class iRequest, + final Class iExecutor, final OCallable iConfigCallback) { + registerExecutor(iRequest, iExecutor); + configCallbacks.put(iRequest, iConfigCallback); + return this; + } + + public OCommandManager registerExecutor(final Class iRequest, + final Class iExecutor) { + commandReqExecMap.put(iRequest, iExecutor); + return this; + } + + public OCommandManager unregisterExecutor(final Class iRequest) { + commandReqExecMap.remove(iRequest); + configCallbacks.remove(iRequest); + return this; + } + + public OCommandExecutor getExecutor(OCommandRequestInternal iCommand) { + final Class executorClass = commandReqExecMap.get(iCommand.getClass()); + + if (executorClass == null) + throw new OCommandExecutorNotFoundException("Cannot find a command executor for the command request: " + iCommand); + + try { + final OCommandExecutor exec = executorClass.newInstance(); + + final OCallable callback = configCallbacks.get(iCommand.getClass()); + if (callback != null) + callback.call(iCommand); + + return exec; + + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Cannot create the command executor of class " + executorClass + + " for the command request: " + iCommand), e); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandOutputListener.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandOutputListener.java new file mode 100644 index 00000000000..9a2349e6274 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandOutputListener.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +/** + * Receives callback from a command as output. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCommandOutputListener { + void onMessage(String iText); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandPredicate.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandPredicate.java new file mode 100644 index 00000000000..16c75c79119 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandPredicate.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Command predicate to be evaluated against a record and a context. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OCommandPredicate { + /** + * Evaluates the predicate. + * + * @param iRecord + * Target record + * @param iCurrentResult TODO + * @param iContext + * Context of execution + * @return The result of predicate + */ + public Object evaluate(final OIdentifiable iRecord, ODocument iCurrentResult, final OCommandContext iContext); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandProcess.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandProcess.java new file mode 100644 index 00000000000..eb5486f7251 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandProcess.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.command; + +/** + * Base command processing class. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OCommandProcess { + protected final C command; + protected T target; + + /** + * Create the process defining command and target. + */ + public OCommandProcess(final C iCommand, final T iTarget) { + command = iCommand; + target = iTarget; + } + + public abstract R process(); + + public T getTarget() { + return target; + } + + @Override + public String toString() { + return target.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequest.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequest.java new file mode 100644 index 00000000000..3baa931f5f5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequest.java @@ -0,0 +1,132 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.command.OCommandContext.TIMEOUT_STRATEGY; + +/** + * Generic GOF command pattern implementation. Execute a command passing the optional arguments "iArgs" and returns an Object. + * + * @author Luca Garulli + */ +public interface OCommandRequest { + RET execute(Object... iArgs); + + /** + * This api is deprecated use sql keyword "LIMIT" instead + * + * Returns the limit of result set. -1 means no limits. + * + */ + + int getLimit(); + + /** + * This api is deprecated use sql keyword "LIMIT" instead + * + * Sets the maximum items the command can returns. -1 means no limits. + * + * @param iLimit + * -1 = no limit. 1 to N to limit the result set. + * @return + */ + @Deprecated + OCommandRequest setLimit(int iLimit); + + /** + * This api is deprecated use sql keyword "TIMEOUT" instead + * + * Returns the command timeout. 0 means no timeout. + * + * @return + */ + @Deprecated + long getTimeoutTime(); + + /** + * This api is deprecated use sql keyword "TIMEOUT" instead + * + * Returns the command timeout strategy between the defined ones. + * + * @return + */ + @Deprecated + TIMEOUT_STRATEGY getTimeoutStrategy(); + + /** + * This api is deprecated use sql keyword "TIMEOUT" instead + * + * Sets the command timeout. When the command execution time is major than the timeout the command returns + * + * @param timeout + */ + @Deprecated + void setTimeout(long timeout, TIMEOUT_STRATEGY strategy); + + /** + * Returns true if the command doesn't change the database, otherwise false. + */ + boolean isIdempotent(); + + /** + * This api is deprecated use sql keyword "FETCHPLAN" instead + * + * Returns the fetch plan if any + * + * @return Fetch plan as unique string or null if it was not defined. + */ + @Deprecated + String getFetchPlan(); + + /** + * This api is deprecated use sql keyword "FETCHPLAN" instead + * + * Set the fetch plan. The format is: + * + *
      +   * <field>:<depth-level>*
      +   * 
      + * + * Where: + *
        + *
      • field is the name of the field to specify the depth-level. * wildcard means any fields
      • + *
      • depth-level is the depth level to fetch. -1 means infinite, 0 means no fetch at all and 1-N the depth level value.
      • + *
      + * Uses the blank spaces to separate the fields strategies.
      + * Example: + * + *
      +   * children:-1 parent:0 sibling:3 *:0
      +   * 
      + * + *
      + * + * @param iFetchPlan + * @return + */ + @Deprecated + RET setFetchPlan(String iFetchPlan); + + void setUseCache(boolean iUseCache); + + OCommandContext getContext(); + + OCommandRequest setContext(final OCommandContext iContext); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAbstract.java new file mode 100755 index 00000000000..15f413d08dd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAbstract.java @@ -0,0 +1,246 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.command.OCommandContext.TIMEOUT_STRATEGY; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.replication.OAsyncReplicationError; +import com.orientechnologies.orient.core.replication.OAsyncReplicationOk; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Text based Command Request abstract class. + * + * @author Luca Garulli + */ +@SuppressWarnings("serial") +public abstract class OCommandRequestAbstract implements OCommandRequestInternal, ODistributedCommand { + protected OCommandResultListener resultListener; + protected OProgressListener progressListener; + protected int limit = -1; + protected long timeoutMs = OGlobalConfiguration.COMMAND_TIMEOUT.getValueAsLong(); + protected TIMEOUT_STRATEGY timeoutStrategy = TIMEOUT_STRATEGY.EXCEPTION; + protected Map parameters; + protected String fetchPlan = null; + protected boolean useCache = false; + protected boolean cacheableResult = false; + protected OCommandContext context; + protected OAsyncReplicationOk onAsyncReplicationOk; + protected OAsyncReplicationError onAsyncReplicationError; + + private final Set nodesToExclude = new HashSet(); + private boolean recordResultSet = true; + + + protected OCommandRequestAbstract() { + } + + public OCommandResultListener getResultListener() { + return resultListener; + } + + public void setResultListener(OCommandResultListener iListener) { + resultListener = iListener; + } + + public Map getParameters() { + return parameters; + } + + protected void setParameters(final Object... iArgs) { + if (iArgs != null && iArgs.length>0) + parameters = convertToParameters(iArgs); + } + + @SuppressWarnings("unchecked") + protected Map convertToParameters(Object... iArgs) { + final Map params; + + if (iArgs.length == 1 && iArgs[0] instanceof Map) { + params = (Map) iArgs[0]; + } else { + if (iArgs.length == 1 && iArgs[0] != null && iArgs[0].getClass().isArray() && iArgs[0] instanceof Object[]) + iArgs = (Object[]) iArgs[0]; + + params = new HashMap(iArgs.length); + for (int i = 0; i RET setFetchPlan(String fetchPlan) { + this.fetchPlan = fetchPlan; + return (RET) this; + } + + public boolean isUseCache() { + return useCache; + } + + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + @Override + public boolean isCacheableResult() { + return cacheableResult; + } + + @Override + public void setCacheableResult(final boolean iValue) { + cacheableResult = iValue; + } + + @Override + public OCommandContext getContext() { + if (context == null) + context = new OBasicCommandContext(); + return context; + } + + public OCommandRequestAbstract setContext(final OCommandContext iContext) { + context = iContext; + return this; + } + + public long getTimeoutTime() { + return timeoutMs; + } + + public void setTimeout(final long timeout, final TIMEOUT_STRATEGY strategy) { + this.timeoutMs = timeout; + this.timeoutStrategy = strategy; + } + + public TIMEOUT_STRATEGY getTimeoutStrategy() { + return timeoutStrategy; + } + + @Override + public Set nodesToExclude() { + return Collections.unmodifiableSet(nodesToExclude); + } + + public void addExcludedNode(String node) { + nodesToExclude.add(node); + } + + public void removeExcludedNode(String node) { + nodesToExclude.remove(node); + } + + + public OAsyncReplicationOk getOnAsyncReplicationOk() { + return onAsyncReplicationOk; + } + + public OAsyncReplicationError getOnAsyncReplicationError() { + return onAsyncReplicationError; + } + + + @Override + public void setRecordResultSet(boolean recordResultSet) { + this.recordResultSet = recordResultSet; + } + + public boolean isRecordResultSet() { + return recordResultSet; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAsynch.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAsynch.java new file mode 100644 index 00000000000..387c558a6f9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestAsynch.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +public interface OCommandRequestAsynch { + public OCommandResultListener getResultListener(); + + public void setResultListener(OCommandResultListener iListener); + + public boolean isAsynchronous(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestInternal.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestInternal.java new file mode 100644 index 00000000000..fee555bfd37 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestInternal.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +import java.util.Map; + +/** + * Internal specialization of generic OCommand interface. + * + * @author Luca Garulli + * + */ +public interface OCommandRequestInternal extends OCommandRequest, OSerializableStream { + + Map getParameters(); + + OCommandResultListener getResultListener(); + + void setResultListener(OCommandResultListener iListener); + + OProgressListener getProgressListener(); + + OCommandRequestInternal setProgressListener(OProgressListener iProgressListener); + + void reset(); + + boolean isCacheableResult(); + + void setCacheableResult(boolean iValue); + + /** + * Communicate to a listener if the result set is an record based or anything else + * + * @param recordResultSet + */ + void setRecordResultSet(boolean recordResultSet); + + boolean isRecordResultSet(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestText.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestText.java new file mode 100644 index 00000000000..883616cc4c5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestText.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +/** + * Internal specialization of generic OCommand interface based on a text command. + * + * @author Luca Garulli + */ +public interface OCommandRequestText extends OCommandRequestInternal, OSerializableStream { + String getText(); + + OCommandRequestText setText(String iText); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestTextAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestTextAbstract.java new file mode 100755 index 00000000000..86a68a60762 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandRequestTextAbstract.java @@ -0,0 +1,209 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OExecutionThreadLocal; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.index.OCompositeKey; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OMemoryStream; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.serialization.serializer.ONetworkThreadLocalSerializer; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.index.OCompositeKeySerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerStringAbstract; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Text based Command Request abstract class. + * + * @author Luca Garulli + */ +@SuppressWarnings("serial") +public abstract class OCommandRequestTextAbstract extends OCommandRequestAbstract implements OCommandRequestText { + protected String text; + + protected OCommandRequestTextAbstract() { + } + + protected OCommandRequestTextAbstract(final String iText) { + if (iText == null) + throw new IllegalArgumentException("Text cannot be null"); + + text = iText.trim(); + } + + /** + * Delegates the execution to the configured command executor. + */ + @SuppressWarnings("unchecked") + public RET execute(final Object... iArgs) { + setParameters(iArgs); + + OExecutionThreadLocal.INSTANCE.get().onAsyncReplicationOk = onAsyncReplicationOk; + OExecutionThreadLocal.INSTANCE.get().onAsyncReplicationError = onAsyncReplicationError; + + return (RET) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().command(this); + } + + public String getText() { + return text; + } + + public OCommandRequestText setText(final String iText) { + this.text = iText; + return this; + } + + public OSerializableStream fromStream(final byte[] iStream) throws OSerializationException { + final OMemoryStream buffer = new OMemoryStream(iStream); + fromStream(buffer); + return this; + } + + public byte[] toStream() throws OSerializationException { + final OMemoryStream buffer = new OMemoryStream(); + return toStream(buffer); + } + + @Override + public String toString() { + return "?." + text; + } + + protected byte[] toStream(final OMemoryStream buffer) { + buffer.setUtf8(text); + + if (parameters == null || parameters.size() == 0) { + // simple params are absent + buffer.set(false); + // composite keys are absent + buffer.set(false); + } else { + final Map params = new HashMap(); + final Map> compositeKeyParams = new HashMap>(); + + for (final Entry paramEntry : parameters.entrySet()) + if (paramEntry.getValue() instanceof OCompositeKey) { + final OCompositeKey compositeKey = (OCompositeKey) paramEntry.getValue(); + compositeKeyParams.put(paramEntry.getKey(), compositeKey.getKeys()); + } else + params.put(paramEntry.getKey(), paramEntry.getValue()); + + buffer.set(!params.isEmpty()); + if (!params.isEmpty()) { + final ODocument param = new ODocument(); + param.field("parameters", params); + buffer.set(param.toStream()); + } + + buffer.set(!compositeKeyParams.isEmpty()); + if (!compositeKeyParams.isEmpty()) { + final ODocument compositeKey = new ODocument(); + compositeKey.field("compositeKeyParams", compositeKeyParams); + buffer.set(compositeKey.toStream()); + } + } + + return buffer.toByteArray(); + } + + protected void fromStream(final OMemoryStream buffer) { + text = buffer.getAsString(); + + parameters = null; + + ORecordSerializer serializer = ONetworkThreadLocalSerializer.getNetworkSerializer(); + + final boolean simpleParams = buffer.getAsBoolean(); + if (simpleParams) { + final byte[] paramBuffer = buffer.getAsByteArray(); + final ODocument param = new ODocument(); + if (serializer != null) + serializer.fromStream(paramBuffer, param, null); + else + param.fromStream(paramBuffer); + + Map params = param.field("params"); + parameters = new HashMap(); + if (params != null) { + for (Entry p : params.entrySet()) { + final Object value; + if (p.getValue() instanceof String) + value = ORecordSerializerStringAbstract.getTypeValue((String) p.getValue()); + else + value = p.getValue(); + + if (p.getKey() instanceof String && Character.isDigit(((String) p.getKey()).charAt(0))) + parameters.put(Integer.parseInt((String) p.getKey()), value); + else + parameters.put(p.getKey(), value); + } + } else { + params = param.field("parameters"); + for (Entry p : params.entrySet()) { + if (p.getKey() instanceof String && Character.isDigit(((String) p.getKey()).charAt(0))) + parameters.put(Integer.parseInt((String) p.getKey()), p.getValue()); + else + parameters.put(p.getKey(), p.getValue()); + } + } + } + + final boolean compositeKeyParamsPresent = buffer.getAsBoolean(); + if (compositeKeyParamsPresent) { + final byte[] paramBuffer = buffer.getAsByteArray(); + final ODocument param = new ODocument(); + if (serializer != null) + serializer.fromStream(paramBuffer, param, null); + else + param.fromStream(paramBuffer); + + final Map compositeKeyParams = param.field("compositeKeyParams"); + + if (parameters == null) + parameters = new HashMap(); + + for (final Entry p : compositeKeyParams.entrySet()) { + if (p.getValue() instanceof List) { + final OCompositeKey compositeKey = new OCompositeKey((List) p.getValue()); + if (p.getKey() instanceof String && Character.isDigit(((String) p.getKey()).charAt(0))) + parameters.put(Integer.parseInt((String) p.getKey()), compositeKey); + else + parameters.put(p.getKey(), compositeKey); + + } else { + final Object value = OCompositeKeySerializer.INSTANCE.deserialize(OStringSerializerHelper.getBinaryContent(p.getValue()), 0); + + if (p.getKey() instanceof String && Character.isDigit(((String) p.getKey()).charAt(0))) + parameters.put(Integer.parseInt((String) p.getKey()), value); + else + parameters.put(p.getKey(), value); + } + } + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/OCommandResultListener.java b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandResultListener.java new file mode 100644 index 00000000000..8ff9a4e6519 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/OCommandResultListener.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command; + +/** + * Callback interface called when the command returns results. + * + * @author Luca Garulli + * + */ +public interface OCommandResultListener { + /** + * This method is called for each result. + * + * @param iRecord + * Current record + * @return True to continue the query, otherwise false + */ + boolean result(Object iRecord); + + /** + * Called at the end of processing. This is useful to clean-up local attributes. + */ + void end(); + + Object getResult(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/ODistributedCommand.java b/core/src/main/java/com/orientechnologies/orient/core/command/ODistributedCommand.java new file mode 100644 index 00000000000..6bad8a45ac4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/ODistributedCommand.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.replication.OAsyncReplicationError; +import com.orientechnologies.orient.core.replication.OAsyncReplicationOk; + +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 7/2/14 + */ +public interface ODistributedCommand { + Set nodesToExclude(); + + /** + * Defines a callback to call in case of the asynchronous replication succeed. + */ + ODistributedCommand onAsyncReplicationOk(OAsyncReplicationOk iCallback); + + /** + * Defines a callback to call in case of error during the asynchronous replication. + */ + ODistributedCommand onAsyncReplicationError(OAsyncReplicationError iCallback); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/ODocumentEqualityWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/command/ODocumentEqualityWrapper.java new file mode 100644 index 00000000000..d8484614bdb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/ODocumentEqualityWrapper.java @@ -0,0 +1,39 @@ +package com.orientechnologies.orient.core.command; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; + +/** + * This class is designed to compare documents based on deep equality (to be used in Sets) + */ +public class ODocumentEqualityWrapper { + private final ODocument internal; + + ODocumentEqualityWrapper(ODocument internal) { + + this.internal = internal; + } + + public boolean equals(Object obj) { + if(obj instanceof ODocumentEqualityWrapper) { + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + return ODocumentHelper.hasSameContentOf(internal, db, ((ODocumentEqualityWrapper)obj).internal, db, null); + } + return false; + } + + @Override + public int hashCode() { + int result = 0; + for (String fieldName : internal.fieldNames()) { + result += fieldName.hashCode(); + Object value = internal.field(fieldName); + if (value != null) { + result += value.hashCode(); + } + } + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorFunction.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorFunction.java new file mode 100755 index 00000000000..bb74bb6549c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorFunction.java @@ -0,0 +1,130 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.common.concur.resource.OPartitionedObjectPool; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandExecutorAbstract; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; + +import javax.script.*; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Executes Script Commands. + * + * @see OCommandScript + * @author Luca Garulli + * + */ +public class OCommandExecutorFunction extends OCommandExecutorAbstract { + protected OCommandFunction request; + + public OCommandExecutorFunction() { + } + + @SuppressWarnings("unchecked") + public OCommandExecutorFunction parse(final OCommandRequest iRequest) { + request = (OCommandFunction) iRequest; + return this; + } + + public Object execute(final Map iArgs) { + return executeInContext(null, iArgs); + } + + public Object executeInContext(final OCommandContext iContext, final Map iArgs) { + + parserText = request.getText(); + + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OFunction f = db.getMetadata().getFunctionLibrary().getFunction(parserText); + + db.checkSecurity(ORule.ResourceGeneric.FUNCTION, ORole.PERMISSION_READ, f.getName()); + + final OScriptManager scriptManager = Orient.instance().getScriptManager(); + + final OPartitionedObjectPool.PoolEntry entry = scriptManager.acquireDatabaseEngine(db.getName(), f.getLanguage()); + final ScriptEngine scriptEngine = entry.object; + try { + final Bindings binding = scriptManager.bind(scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE), (ODatabaseDocumentTx) db, + iContext, iArgs); + + try { + final Object result; + + if (scriptEngine instanceof Invocable) { + // INVOKE AS FUNCTION. PARAMS ARE PASSED BY POSITION + final Invocable invocableEngine = (Invocable) scriptEngine; + Object[] args = null; + if (iArgs != null) { + args = new Object[iArgs.size()]; + int i = 0; + for (Entry arg : iArgs.entrySet()) + args[i++] = arg.getValue(); + } else { + args = OCommonConst.EMPTY_OBJECT_ARRAY; + } + result = invocableEngine.invokeFunction(parserText, args); + + } else { + // INVOKE THE CODE SNIPPET + final Object[] args = iArgs == null ? null : iArgs.values().toArray(); + result = scriptEngine.eval(scriptManager.getFunctionInvoke(f, args), binding); + } + + return OCommandExecutorUtility.transformResult(result); + + } catch (ScriptException e) { + throw OException.wrapException( + new OCommandScriptException("Error on execution of the script", request.getText(), e.getColumnNumber()), e); + } catch (NoSuchMethodException e) { + throw OException.wrapException(new OCommandScriptException("Error on execution of the script", request.getText(), 0), e); + } catch (OCommandScriptException e) { + // PASS THROUGH + throw e; + + } finally { + scriptManager.unbind(binding, iContext, iArgs); + } + } finally { + scriptManager.releaseDatabaseEngine(f.getLanguage(), db.getName(), entry); + } + } + + public boolean isIdempotent() { + return false; + } + + @Override + protected void throwSyntaxErrorException(String iText) { + throw new OCommandScriptException("Error on execution of the script: " + iText, request.getText(), 0); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorScript.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorScript.java new file mode 100755 index 00000000000..88e8dd15e8c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorScript.java @@ -0,0 +1,640 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.concur.resource.OPartitionedObjectPool; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.parser.OContextVariableResolver; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.*; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.exception.OTransactionException; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.OTemporaryRidGenerator; +import com.orientechnologies.orient.core.sql.filter.OSQLFilter; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; +import com.orientechnologies.orient.core.sql.parser.OIfStatement; +import com.orientechnologies.orient.core.sql.parser.OStatement; +import com.orientechnologies.orient.core.sql.parser.OrientSql; +import com.orientechnologies.orient.core.sql.parser.ParseException; +import com.orientechnologies.orient.core.sql.query.OResultSet; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; +import com.orientechnologies.orient.core.tx.OTransaction; + +import javax.script.*; +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Executes Script Commands. + * + * @author Luca Garulli + * @see OCommandScript + */ +public class OCommandExecutorScript extends OCommandExecutorAbstract implements OCommandDistributedReplicateRequest, OTemporaryRidGenerator { + private static final int MAX_DELAY = 100; + protected OCommandScript request; + protected DISTRIBUTED_EXECUTION_MODE executionMode = DISTRIBUTED_EXECUTION_MODE.LOCAL; + protected AtomicInteger serialTempRID = new AtomicInteger(0); + + public OCommandExecutorScript() { + } + + @SuppressWarnings("unchecked") + public OCommandExecutorScript parse(final OCommandRequest iRequest) { + request = (OCommandScript) iRequest; + executionMode = ((OCommandScript) iRequest).getExecutionMode(); + return this; + } + + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return executionMode; + } + + public Object execute(final Map iArgs) { + if (context == null) + context = new OBasicCommandContext(); + return executeInContext(context, iArgs); + } + + public Object executeInContext(final OCommandContext iContext, final Map iArgs) { + final String language = request.getLanguage(); + parserText = request.getText(); + parameters = iArgs; + + parameters = iArgs; + if (language.equalsIgnoreCase("SQL")) { + // SPECIAL CASE: EXECUTE THE COMMANDS IN SEQUENCE + try { + parserText = preParse(parserText, iArgs); + } catch (ParseException e) { + throw new OCommandExecutionException("Invalid script:" + e.getMessage()); + } + return executeSQL(); + } else { + return executeJsr223Script(language, iContext, iArgs); + } + } + + private String preParse(String parserText, final Map iArgs) throws ParseException { + final boolean strict = getDatabase().getStorage().getConfiguration().isStrictSql(); + if (strict) { + parserText = addSemicolons(parserText); + InputStream is = new ByteArrayInputStream(parserText.getBytes()); + OrientSql osql = new OrientSql(is); + List statements = osql.parseScript(); + StringBuilder result = new StringBuilder(); + for (OStatement stm : statements) { + stm.toString(iArgs, result); + if (!(stm instanceof OIfStatement)) { + result.append(";"); + } + result.append("\n"); + } + return result.toString(); + } else { + return parserText; + } + } + + private String addSemicolons(String parserText) { + String[] rows = parserText.split("\n"); + StringBuilder builder = new StringBuilder(); + for (String row : rows) { + row = row.trim(); + builder.append(row); + if (!(row.endsWith(";") || row.endsWith("{"))) { + builder.append(";"); + } + builder.append("\n"); + } + return builder.toString(); + } + + public boolean isIdempotent() { + return false; + } + + protected Object executeJsr223Script(final String language, final OCommandContext iContext, final Map iArgs) { + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + + final OScriptManager scriptManager = Orient.instance().getScriptManager(); + CompiledScript compiledScript = request.getCompiledScript(); + + final OPartitionedObjectPool.PoolEntry entry = scriptManager.acquireDatabaseEngine(db.getName(), language); + final ScriptEngine scriptEngine = entry.object; + try { + + if (compiledScript == null) { + if (!(scriptEngine instanceof Compilable)) + throw new OCommandExecutionException("Language '" + language + "' does not support compilation"); + + final Compilable c = (Compilable) scriptEngine; + try { + compiledScript = c.compile(parserText); + } catch (ScriptException e) { + scriptManager.throwErrorMessage(e, parserText); + } + + request.setCompiledScript(compiledScript); + } + + final Bindings binding = scriptManager.bind(compiledScript.getEngine().getBindings(ScriptContext.ENGINE_SCOPE), + (ODatabaseDocumentTx) db, iContext, iArgs); + + try { + final Object ob = compiledScript.eval(binding); + + return OCommandExecutorUtility.transformResult(ob); + } catch (ScriptException e) { + throw OException.wrapException( + new OCommandScriptException("Error on execution of the script", request.getText(), e.getColumnNumber()), e); + + } finally { + scriptManager.unbind(binding, iContext, iArgs); + } + } finally { + scriptManager.releaseDatabaseEngine(language, db.getName(), entry); + } + } + + // TODO: CREATE A REGULAR JSR223 SCRIPT IMPL + protected Object executeSQL() { + ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + + return executeSQLScript(parserText, db); + + } catch (IOException e) { + throw OException.wrapException(new OCommandExecutionException("Error on executing command: " + parserText), e); + } + } + + @Override + protected void throwSyntaxErrorException(String iText) { + throw new OCommandScriptException("Error on execution of the script: " + iText, request.getText(), 0); + } + + protected Object executeSQLScript(final String iText, final ODatabaseDocument db) throws IOException { + Object lastResult = null; + int maxRetry = 1; + + context.setVariable("transactionRetries", 0); + context.setVariable("parentQuery", this); + + for (int retry = 1; retry <= maxRetry; retry++) { + try { + try { + int txBegunAtLine = -1; + int txBegunAtPart = -1; + lastResult = null; + int nestedLevel = 0; + int skippingScriptsAtNestedLevel = -1; + + final BufferedReader reader = new BufferedReader(new StringReader(iText)); + + int line = 0; + int linePart = 0; + String lastLine; + boolean txBegun = false; + + for (; line < txBegunAtLine; ++line) + // SKIP PREVIOUS COMMAND AND JUMP TO THE BEGIN IF ANY + reader.readLine(); + + for (; (lastLine = reader.readLine()) != null; ++line) { + lastLine = lastLine.trim(); + + // this block is here (and not below, with the other conditions) + // just because of the smartSprit() that does not parse correctly a single bracket + + // final List lineParts = OStringSerializerHelper.smartSplit(lastLine, ';', true); + final List lineParts = splitBySemicolon(lastLine); + + if (line == txBegunAtLine) + // SKIP PREVIOUS COMMAND PART AND JUMP TO THE BEGIN IF ANY + linePart = txBegunAtPart; + else + linePart = 0; + + boolean breakReturn = false; + + for (; linePart < lineParts.size(); ++linePart) { + final String lastCommand = lineParts.get(linePart); + + if (isIfCondition(lastCommand)) { + nestedLevel++; + if (skippingScriptsAtNestedLevel >= 0) { + continue; // I'm in an (outer) IF that did not match the condition + } + boolean ifResult = evaluateIfCondition(lastCommand); + if (!ifResult) { + // if does not match the condition, skip all the inner statements + skippingScriptsAtNestedLevel = nestedLevel; + } + continue; + } else if (lastCommand.equals("}")) { + nestedLevel--; + if (skippingScriptsAtNestedLevel > nestedLevel) { + skippingScriptsAtNestedLevel = -1; + } + continue; + } else if (skippingScriptsAtNestedLevel >= 0) { + continue; // I'm in an IF that did not match the condition + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "let ")) { + lastResult = executeLet(lastCommand, db); + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "begin")) { + + if (txBegun) + throw new OCommandSQLParsingException("Transaction already begun"); + + if (db.getTransaction().isActive()) + // COMMIT ANY ACTIVE TX + db.commit(); + + txBegun = true; + txBegunAtLine = line; + txBegunAtPart = linePart; + + db.begin(); + + if (lastCommand.length() > "begin ".length()) { + String next = lastCommand.substring("begin ".length()).trim(); + if (OStringSerializerHelper.startsWithIgnoreCase(next, "isolation ")) { + next = next.substring("isolation ".length()).trim(); + db.getTransaction().setIsolationLevel(OTransaction.ISOLATION_LEVEL.valueOf(next.toUpperCase(Locale.ENGLISH))); + } + } + + } else if ("rollback".equalsIgnoreCase(lastCommand)) { + + if (!txBegun) + throw new OCommandSQLParsingException("Transaction not begun"); + + db.rollback(); + + txBegun = false; + txBegunAtLine = -1; + txBegunAtPart = -1; + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "commit")) { + if (txBegunAtLine < 0) + throw new OCommandSQLParsingException("Transaction not begun"); + + if (retry == 1 && lastCommand.length() > "commit ".length()) { + // FIRST CYCLE: PARSE RETRY TIMES OVERWRITING DEFAULT = 1 + String next = lastCommand.substring("commit ".length()).trim(); + if (OStringSerializerHelper.startsWithIgnoreCase(next, "retry ")) { + next = next.substring("retry ".length()).trim(); + maxRetry = Integer.parseInt(next); + } + } + + db.commit(); + + txBegun = false; + txBegunAtLine = -1; + txBegunAtPart = -1; + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "sleep ")) { + executeSleep(lastCommand); + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "console.log ")) { + executeConsoleLog(lastCommand, db); + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "console.output ")) { + executeConsoleOutput(lastCommand, db); + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "console.error ")) { + executeConsoleError(lastCommand, db); + + } else if (OStringSerializerHelper.startsWithIgnoreCase(lastCommand, "return ")) { + lastResult = getValue(lastCommand.substring("return ".length()), db); + + // END OF SCRIPT + breakReturn = true; + break; + + } else if (lastCommand != null && lastCommand.length() > 0) + lastResult = executeCommand(lastCommand, db); + } + if (breakReturn) { + break; + } + } + } catch (RuntimeException ex) { + if (db.getTransaction().isActive()) + db.rollback(); + throw ex; + } + + // COMPLETED + break; + + } catch (OTransactionException e) { + // THIS CASE IS ON UPSERT + context.setVariable("retries", retry); + getDatabase().getLocalCache().clear(); + if (retry >= maxRetry) + throw e; + + waitForNextRetry(); + + } catch (ORecordDuplicatedException e) { + // THIS CASE IS ON UPSERT + context.setVariable("retries", retry); + getDatabase().getLocalCache().clear(); + if (retry >= maxRetry) + throw e; + + waitForNextRetry(); + + } catch (ORecordNotFoundException e) { + // THIS CASE IS ON UPSERT + context.setVariable("retries", retry); + getDatabase().getLocalCache().clear(); + if (retry >= maxRetry) + throw e; + + } catch (ONeedRetryException e) { + context.setVariable("retries", retry); + getDatabase().getLocalCache().clear(); + if (retry >= maxRetry) + throw e; + + waitForNextRetry(); + } + } + + return lastResult; + } + + private List splitBySemicolon(String lastLine) { + if (lastLine == null) { + return Collections.EMPTY_LIST; + } + List result = new ArrayList(); + Character prev = null; + Character lastQuote = null; + StringBuilder buffer = new StringBuilder(); + for (char c : lastLine.toCharArray()) { + if (c == ';' && lastQuote == null) { + if (buffer.toString().trim().length() > 0) { + result.add(buffer.toString().trim()); + } + buffer = new StringBuilder(); + prev = null; + continue; + } + if ((c == '"' || c == '\'') && (prev == null || !prev.equals('\\'))) { + if (lastQuote != null && lastQuote.equals(c)) { + lastQuote = null; + } else if (lastQuote == null) { + lastQuote = c; + } + } + buffer.append(c); + prev = c; + } + if (buffer.toString().trim().length() > 0) { + result.add(buffer.toString().trim()); + } + return result; + } + + private boolean evaluateIfCondition(String lastCommand) { + String cmd = lastCommand; + cmd = cmd.trim().substring(2);// remove IF + cmd = cmd.trim().substring(0, cmd.trim().length() - 1); // remove { + OSQLFilter condition = OSQLEngine.getInstance().parseCondition(cmd, getContext(), "IF"); + Object result = null; + try { + result = condition.evaluate(null, null, getContext()); + } catch (Exception e) { + throw new OCommandExecutionException("Could not evaluate IF condition: " + cmd + " - " + e.getMessage()); + } + + if (Boolean.TRUE.equals(result)) { + return true; + } + return false; + } + + private boolean isIfCondition(String iCommand) { + if (iCommand == null) { + return false; + } + String cmd = iCommand.trim(); + if (cmd.length() < 3) { + return false; + } + if (!((OStringSerializerHelper.startsWithIgnoreCase(cmd, "if ")) || OStringSerializerHelper.startsWithIgnoreCase(cmd, "if("))) { + return false; + } + if (!cmd.endsWith("{")) { + return false; + } + return true; + } + + /** + * Wait before to retry + */ + protected void waitForNextRetry() { + try { + Thread.sleep(new Random().nextInt(MAX_DELAY - 1) + 1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + OLogManager.instance().error(this, "Wait was interrupted", e); + } + } + + private Object executeCommand(final String lastCommand, final ODatabaseDocument db) { + final OCommandSQL command = new OCommandSQL(lastCommand); + Object result = db.command(command.setContext(getContext())).execute(toMap(parameters)); + request.setFetchPlan(command.getFetchPlan()); + return result; + } + + private Object toMap(Object parameters) { + if (parameters instanceof SimpleBindings) { + HashMap result = new LinkedHashMap(); + result.putAll((SimpleBindings) parameters); + return result; + } + return parameters; + } + + private Object getValue(final String iValue, final ODatabaseDocument db) { + Object lastResult = null; + boolean recordResultSet = true; + if (iValue.equalsIgnoreCase("NULL")) + lastResult = null; + else if (iValue.startsWith("[") && iValue.endsWith("]")) { + // ARRAY - COLLECTION + final List items = new ArrayList(); + + OStringSerializerHelper.getCollection(iValue, 0, items); + final List result = new ArrayList(items.size()); + + for (int i = 0; i < items.size(); ++i) { + String item = items.get(i); + + result.add(getValue(item, db)); + } + lastResult = result; + checkIsRecordResultSet(lastResult); + } else if (iValue.startsWith("{") && iValue.endsWith("}")) { + // MAP + final Map map = OStringSerializerHelper.getMap(iValue); + final Map result = new HashMap(map.size()); + + for (Map.Entry entry : map.entrySet()) { + // KEY + String stringKey = entry.getKey(); + if (stringKey == null) + continue; + + stringKey = stringKey.trim(); + + Object key; + if (stringKey.startsWith("$")) + key = getContext().getVariable(stringKey); + else + key = stringKey; + + if (OMultiValue.isMultiValue(key) && OMultiValue.getSize(key) == 1) + key = OMultiValue.getFirstValue(key); + + // VALUE + String stringValue = entry.getValue(); + if (stringValue == null) + continue; + + stringValue = stringValue.trim(); + + Object value; + if (stringValue.toString().startsWith("$")) + value = getContext().getVariable(stringValue); + else + value = stringValue; + + result.put(key, value); + } + lastResult = result; + checkIsRecordResultSet(lastResult); + } else if (iValue.startsWith("\"") && iValue.endsWith("\"") || iValue.startsWith("'") && iValue.endsWith("'")) { + lastResult = new OContextVariableResolver(context).parse(OIOUtils.getStringContent(iValue)); + checkIsRecordResultSet(lastResult); + } else if (iValue.startsWith("(") && iValue.endsWith(")")) + lastResult = executeCommand(iValue.substring(1, iValue.length() - 1), db); + else { + lastResult = new OSQLPredicate(iValue).evaluate(context); + + } + // END OF THE SCRIPT + return lastResult; + } + + private void checkIsRecordResultSet(Object result) { + if (!(result instanceof OIdentifiable) && !(result instanceof OResultSet)) { + if (!OMultiValue.isMultiValue(result)) { + request.setRecordResultSet(false); + } else { + for (Object val : OMultiValue.getMultiValueIterable(result)) { + if (!(val instanceof OIdentifiable)) + request.setRecordResultSet(false); + } + } + } + } + + private void executeSleep(String lastCommand) { + final String sleepTimeInMs = lastCommand.substring("sleep ".length()).trim(); + try { + Thread.sleep(Integer.parseInt(sleepTimeInMs)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + OLogManager.instance().debug(this, "Sleep was interrupted in SQL batch"); + } + } + + private void executeConsoleLog(final String lastCommand, final ODatabaseDocument db) { + final String value = lastCommand.substring("console.log ".length()).trim(); + OLogManager.instance().info(this, "%s", getValue(OIOUtils.wrapStringContent(value, '\''), db)); + } + + private void executeConsoleOutput(final String lastCommand, final ODatabaseDocument db) { + final String value = lastCommand.substring("console.output ".length()).trim(); + System.out.println(getValue(OIOUtils.wrapStringContent(value, '\''), db)); + } + + private void executeConsoleError(final String lastCommand, final ODatabaseDocument db) { + final String value = lastCommand.substring("console.error ".length()).trim(); + System.err.println(getValue(OIOUtils.wrapStringContent(value, '\''), db)); + } + + private Object executeLet(final String lastCommand, final ODatabaseDocument db) { + final int equalsPos = lastCommand.indexOf('='); + final String variable = lastCommand.substring("let ".length(), equalsPos).trim(); + final String cmd = lastCommand.substring(equalsPos + 1).trim(); + if (cmd == null) + return null; + + Object lastResult = null; + + if (cmd.equalsIgnoreCase("NULL") || cmd.startsWith("$") || (cmd.startsWith("[") && cmd.endsWith("]")) + || (cmd.startsWith("{") && cmd.endsWith("}")) + || (cmd.startsWith("\"") && cmd.endsWith("\"") || cmd.startsWith("'") && cmd.endsWith("'")) + || (cmd.startsWith("(") && cmd.endsWith(")"))) + lastResult = getValue(cmd, db); + else + lastResult = executeCommand(cmd, db); + + // PUT THE RESULT INTO THE CONTEXT + getContext().setVariable(variable, lastResult); + return lastResult; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } + + @Override + public int getTemporaryRIDCounter(OCommandContext iContext) { + return serialTempRID.incrementAndGet(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorUtility.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorUtility.java new file mode 100644 index 00000000000..acecc4e184d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandExecutorUtility.java @@ -0,0 +1,70 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * Script utility class + * + * @see OCommandScript + * @author Luca Garulli + * + */ +public class OCommandExecutorUtility { + private static Method java8MethodIsArray; + static { + try { + java8MethodIsArray = Class.forName("jdk.nashorn.api.scripting.JSObject").getDeclaredMethod("isArray",null); + } catch(Exception e) {} + } + /** + * Manages cross compiler compatibility issues. + * + * @param result + * Result to transform + * @return + */ + public static Object transformResult(Object result) { + if (java8MethodIsArray == null || !(result instanceof Map)) { + return result; + } + // PATCH BY MAT ABOUT NASHORN RETURNING VALUE FOR ARRAYS. + try { + if ((Boolean) java8MethodIsArray.invoke(result)) { + List partial = new ArrayList(((Map) result).values()); + List finalResult = new ArrayList(); + for (Object o : partial) { + finalResult.add(transformResult(o)); + } + return finalResult; + } else { + Map mapResult = (Map) result; + List keys = new ArrayList(mapResult.keySet()); + for (Object key : keys) { + mapResult.put(key, transformResult(mapResult.get(key))); + } + return mapResult; + } + } catch (Exception e) {} + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandFunction.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandFunction.java new file mode 100644 index 00000000000..f4221a66b7f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandFunction.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandRequestTextAbstract; + +/** + * Execute a configured function. + * + * @see OCommandExecutorFunction + * @author Luca Garulli + * + */ +public class OCommandFunction extends OCommandRequestTextAbstract { + private static final long serialVersionUID = 1L; + + public OCommandFunction() { + } + + public OCommandFunction(final String iName) { + super(iName); + } + + public boolean isIdempotent() { + return false; + } + + @Override + public String toString() { + return "function." + text; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScript.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScript.java new file mode 100755 index 00000000000..000650655c7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScript.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequestTextAbstract; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.serialization.OMemoryStream; +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +import javax.script.CompiledScript; + +/** + * Script command request implementation. It just stores the request and delegated the execution to the configured OCommandExecutor. + * + * + * @see OCommandExecutorScript + * @author Luca Garulli + * + */ +@SuppressWarnings("serial") +public class OCommandScript extends OCommandRequestTextAbstract { + private String language; + private CompiledScript compiledScript; + + private OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE executionMode = OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE.LOCAL; + + public OCommandScript() { + useCache = true; + } + + public OCommandScript(final String iLanguage, final String iText) { + super(iText); + setLanguage(iLanguage); + useCache = true; + } + + public OCommandScript(final String iText) { + this("sql", iText); + } + + public boolean isIdempotent() { + return false; + } + + public String getLanguage() { + return language; + } + + public OCommandScript setLanguage(String language) { + if (language == null || language.isEmpty()) { + throw new IllegalArgumentException("Not a valid script language specified: " + language); + } + this.language = language; + return this; + } + + public OSerializableStream fromStream(byte[] iStream) throws OSerializationException { + final OMemoryStream buffer = new OMemoryStream(iStream); + language = buffer.getAsString(); + + // FIX TO HANDLE USAGE OF EXECUTION MODE STARTING FROM v2.1.3 + final int currPosition = buffer.getPosition(); + final String value = buffer.getAsString(); + try { + executionMode = OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE.valueOf(value); + } catch (IllegalArgumentException e) { + // OLD VERSION: RESET TO THE OLD POSITION + buffer.setPosition(currPosition); + } + + fromStream(buffer); + return this; + } + + public byte[] toStream() throws OSerializationException { + final OMemoryStream buffer = new OMemoryStream(); + buffer.setUtf8(language); + buffer.setUtf8(executionMode.name()); + return toStream(buffer); + } + + public CompiledScript getCompiledScript() { + return compiledScript; + } + + public void setCompiledScript(CompiledScript script) { + compiledScript = script; + } + + @Override + public String toString() { + if (language != null) + return language + "." + text; + return "script." + text; + } + + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getExecutionMode() { + return executionMode; + } + + public OCommandScript setExecutionMode(OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE executionMode) { + this.executionMode = executionMode; + return this; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScriptException.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScriptException.java new file mode 100755 index 00000000000..f3147f04a82 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OCommandScriptException.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.orient.core.exception.OCoreException; + +public class OCommandScriptException extends OCoreException { + + private String text; + private int position; + private static final long serialVersionUID = -7430575036316163711L; + + private static String makeMessage(String message, int position, String text) { + if (text == null) + return message; + + final StringBuilder buffer = new StringBuilder(); + buffer.append("Error on parsing script at position #"); + buffer.append(position); + buffer.append(": " + message); + buffer.append("\nScript: "); + buffer.append(text); + buffer.append("\n------"); + for (int i = 0; i < position - 1; ++i) + buffer.append("-"); + + buffer.append("^"); + return buffer.toString(); + } + + public OCommandScriptException(OCommandScriptException exception) { + super(exception); + + this.text = exception.text; + this.position = exception.position; + } + + public OCommandScriptException(String iMessage) { + super(iMessage); + } + + public OCommandScriptException(String iMessage, String iText, int iPosition) { + super(makeMessage(iMessage, iPosition < 0 ? 0 : iPosition, iText)); + text = iText; + position = iPosition < 0 ? 0 : iPosition; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/ODatabaseScriptManager.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/ODatabaseScriptManager.java new file mode 100644 index 00000000000..8b24e3c7b01 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/ODatabaseScriptManager.java @@ -0,0 +1,101 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.common.concur.resource.OPartitionedObjectPool; +import com.orientechnologies.common.concur.resource.OPartitionedObjectPoolFactory; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +/** + * Manages Script engines per database. Parsing of function library is done only the first time and when changes. + * + * @see OCommandScript + * @author Luca Garulli + * + */ +public class ODatabaseScriptManager { + private final OScriptManager scriptManager; + protected OPartitionedObjectPoolFactory pooledEngines; + + public ODatabaseScriptManager(final OScriptManager iScriptManager, final String iDatabaseName) { + scriptManager = iScriptManager; + + pooledEngines = new OPartitionedObjectPoolFactory( + new OPartitionedObjectPoolFactory.ObjectFactoryFactory() { + @Override + public OPartitionedObjectPool.ObjectFactory create(final String language) { + return new OPartitionedObjectPool.ObjectFactory() { + @Override + public ScriptEngine create() { + final ScriptEngine scriptEngine = scriptManager.getEngine(language); + final String library = scriptManager.getLibrary(ODatabaseRecordThreadLocal.INSTANCE.get(), language); + + if (library != null) + try { + scriptEngine.eval(library); + } catch (ScriptException e) { + scriptManager.throwErrorMessage(e, library); + } + + return scriptEngine; + } + + @Override + public void init(ScriptEngine object) { + } + + @Override + public void close(ScriptEngine object) { + } + + @Override + public boolean isValid(ScriptEngine object) { + if (language.equals("sql")) { + if (!language.equals(object.getFactory().getLanguageName())) + return false; + } else { + if ((object.getFactory().getLanguageName()).equals("sql")) + return false; + } + return true; + } + }; + } + }); + pooledEngines.setMaxPoolSize(OGlobalConfiguration.SCRIPT_POOL.getValueAsInteger()); + pooledEngines.setMaxPartitions(1); + } + + public OPartitionedObjectPool.PoolEntry acquireEngine(final String language) { + return pooledEngines.get(language).acquire(); + } + + public void releaseEngine(final String language, final OPartitionedObjectPool.PoolEntry entry) { + pooledEngines.get(language).release(entry); + } + + public void close() { + pooledEngines.close(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptDocumentDatabaseWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptDocumentDatabaseWrapper.java new file mode 100644 index 00000000000..cc2a42dc040 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptDocumentDatabaseWrapper.java @@ -0,0 +1,416 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabase.ATTRIBUTES; +import com.orientechnologies.orient.core.db.ODatabase.OPERATION_MODE; +import com.orientechnologies.orient.core.db.ODatabase.STATUS; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.intent.OIntent; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorCluster; +import com.orientechnologies.orient.core.metadata.OMetadata; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.query.OSQLQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import com.orientechnologies.orient.core.storage.ORecordCallback; +import com.orientechnologies.orient.core.tx.OTransaction; + +/** + * Document Database wrapper class to use from scripts. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("unchecked") +public class OScriptDocumentDatabaseWrapper { + protected ODatabaseDocumentTx database; + + public OScriptDocumentDatabaseWrapper(final ODatabaseDocumentTx database) { + this.database = database; + } + + public OScriptDocumentDatabaseWrapper(final String iURL) { + this.database = new ODatabaseDocumentTx(iURL); + } + + public void switchUser(final String iUserName, final String iUserPassword) { + if (!database.isClosed()) + database.close(); + database.open(iUserName, iUserPassword); + } + + public OIdentifiable[] query(final String iText) { + return query(iText, (Object[]) null); + } + + public OIdentifiable[] query(final String iText, final Object... iParameters) { + return query(new OSQLSynchQuery(iText), iParameters); + } + + public OIdentifiable[] query(final OSQLQuery iQuery, final Object... iParameters) { + final List res = database.query(iQuery, convertParameters(iParameters)); + if (res == null) + return OCommonConst.EMPTY_IDENTIFIABLE_ARRAY; + return res.toArray(new OIdentifiable[res.size()]); + } + + /** + * To maintain the compatibility with JS API. + */ + public Object executeCommand(final String iText) { + return command(iText, (Object[]) null); + } + + /** + * To maintain the compatibility with JS API. + */ + public Object executeCommand(final String iText, final Object... iParameters) { + return command(iText, iParameters); + } + + public Object command(final String iText) { + return command(iText, (Object[]) null); + } + + public Object command(final String iText, final Object... iParameters) { + Object res = database.command(new OCommandSQL(iText)).execute(convertParameters(iParameters)); + if (res instanceof List) { + final List list = (List) res; + return list.toArray(new OIdentifiable[list.size()]); + } + return res; + } + + public OIndex getIndex(final String iName) { + return database.getMetadata().getIndexManager().getIndex(iName); + } + + public boolean exists() { + return database.exists(); + } + + public ODocument newInstance() { + return database.newInstance(); + } + + public void reload() { + database.reload(); + } + + public ODocument newInstance(String iClassName) { + return database.newInstance(iClassName); + } + + public ORecordIteratorClass browseClass(String iClassName) { + return database.browseClass(iClassName); + } + + public STATUS getStatus() { + return database.getStatus(); + } + + public ORecordIteratorClass browseClass(String iClassName, boolean iPolymorphic) { + return database.browseClass(iClassName, iPolymorphic); + } + + public THISDB setStatus(STATUS iStatus) { + return (THISDB) database.setStatus(iStatus); + } + + public void drop() { + database.drop(); + } + + public String getName() { + return database.getName(); + } + + public String getURL() { + return database.getURL(); + } + + public ORecordIteratorCluster browseCluster(String iClusterName) { + return database.browseCluster(iClusterName); + } + + public boolean isClosed() { + return database.isClosed(); + } + + public THISDB open(String iUserName, String iUserPassword) { + return (THISDB) database.open(iUserName, iUserPassword); + } + + public ODocument save(final Map iObject) { + return database.save(new ODocument().fields(iObject)); + } + + public ODocument save(final String iString) { + // return database.save((ORecord) new ODocument().fromJSON(iString)); + return database.save((ORecord) new ODocument().fromJSON(iString, true)); + } + + public ODocument save(ORecord iRecord) { + return database.save(iRecord); + } + + public boolean dropCluster(String iClusterName, final boolean iTruncate) { + return database.dropCluster(iClusterName, iTruncate); + } + + public THISDB create() { + return (THISDB) database.create(); + } + + public boolean dropCluster(int iClusterId, final boolean iTruncate) { + return database.dropCluster(iClusterId, true); + } + + public void close() { + database.close(); + } + + public int getClusters() { + return database.getClusters(); + } + + public Collection getClusterNames() { + return database.getClusterNames(); + } + + public OTransaction getTransaction() { + return database.getTransaction(); + } + + public ODatabase begin() { + return database.begin(); + } + + public int getClusterIdByName(String iClusterName) { + return database.getClusterIdByName(iClusterName); + } + + public boolean isMVCC() { + return database.isMVCC(); + } + + public String getClusterNameById(int iClusterId) { + return database.getClusterNameById(iClusterId); + } + + public > RET setMVCC(boolean iValue) { + return (RET) database.setMVCC(iValue); + } + + public long getClusterRecordSizeById(int iClusterId) { + return database.getClusterRecordSizeById(iClusterId); + } + + public boolean isValidationEnabled() { + return database.isValidationEnabled(); + } + + public long getClusterRecordSizeByName(String iClusterName) { + return database.getClusterRecordSizeByName(iClusterName); + } + + public RET setValidationEnabled(boolean iValue) { + return (RET) database.setValidationEnabled(iValue); + } + + public OSecurityUser getUser() { + return database.getUser(); + } + + public void setUser(OUser user) { + database.setUser(user); + } + + public ODocument save(ORecord iRecord, OPERATION_MODE iMode, boolean iForceCreate, + final ORecordCallback iRecordCreatedCallback, ORecordCallback iRecordUpdatedCallback) { + return database.save(iRecord, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback); + } + + public OMetadata getMetadata() { + return database.getMetadata(); + } + + public ODictionary getDictionary() { + return database.getDictionary(); + } + + public byte getRecordType() { + return database.getRecordType(); + } + + public ODatabase delete(ORID iRid) { + return database.delete(iRid); + } + + public RET load(ORID iRecordId) { + return (RET) database.load(iRecordId); + } + + public RET load(ORID iRecordId, String iFetchPlan) { + return (RET) database.load(iRecordId, iFetchPlan); + } + + public RET load(ORID iRecordId, String iFetchPlan, boolean iIgnoreCache) { + return (RET) database.load(iRecordId, iFetchPlan, iIgnoreCache); + } + + public RET getRecord(OIdentifiable iIdentifiable) { + return (RET) database.getRecord(iIdentifiable); + } + + public int getDefaultClusterId() { + return database.getDefaultClusterId(); + } + + public RET load(final String iRidAsString) { + return (RET) database.load(new ORecordId(iRidAsString)); + } + + public RET load(ORecord iRecord) { + return (RET) database.load(iRecord); + } + + public boolean declareIntent(OIntent iIntent) { + return database.declareIntent(iIntent); + } + + public RET load(ORecord iRecord, String iFetchPlan) { + return (RET) database.load(iRecord, iFetchPlan); + } + + public RET load(ORecord iRecord, String iFetchPlan, boolean iIgnoreCache) { + return (RET) database.load(iRecord, iFetchPlan, iIgnoreCache); + } + + public ODatabase setDatabaseOwner(ODatabaseInternal iOwner) { + return database.setDatabaseOwner(iOwner); + } + + public void reload(ORecord iRecord) { + database.reload(iRecord); + } + + public void reload(ORecord iRecord, String iFetchPlan, boolean iIgnoreCache) { + database.reload(iRecord, iFetchPlan, iIgnoreCache); + } + + public Object setProperty(String iName, Object iValue) { + return database.setProperty(iName, iValue); + } + + public ODocument save(ORecord iRecord, String iClusterName) { + return database.save(iRecord, iClusterName); + } + + public Object getProperty(String iName) { + return database.getProperty(iName); + } + + public Iterator> getProperties() { + return database.getProperties(); + } + + public Object get(ATTRIBUTES iAttribute) { + return database.get(iAttribute); + } + + public THISDB set(ATTRIBUTES attribute, Object iValue) { + return (THISDB) database.set(attribute, iValue); + } + + public void setInternal(ATTRIBUTES attribute, Object iValue) { + database.setInternal(attribute, iValue); + } + + public boolean isRetainRecords() { + return database.isRetainRecords(); + } + + public ODatabaseDocument setRetainRecords(boolean iValue) { + return database.setRetainRecords(iValue); + } + + public long getSize() { + return database.getSize(); + } + + public ODocument save(ORecord iRecord, String iClusterName, OPERATION_MODE iMode, boolean iForceCreate, + final ORecordCallback iRecordCreatedCallback, ORecordCallback iRecordUpdatedCallback) { + return database.save(iRecord, iClusterName, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback); + } + + public ODatabaseDocumentTx delete(ODocument iRecord) { + return database.delete(iRecord); + } + + public long countClass(String iClassName) { + return database.countClass(iClassName); + } + + public ODatabase commit() { + return database.commit(); + } + + public ODatabase rollback() { + return database.rollback(); + } + + public String getType() { + return database.getType(); + } + + protected Object[] convertParameters(final Object[] iParameters) { + if (iParameters != null) + for (int i = 0; i < iParameters.length; ++i) { + final Object p = iParameters[i]; + if (p != null) { + // if (p instanceof sun.org.mozilla.javascript.internal.IdScriptableObject) { + // iParameters[i] = ((sun.org.mozilla.javascript.internal.NativeDate) p).to; + // } + } + } + return iParameters; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptInjection.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptInjection.java new file mode 100644 index 00000000000..55d736c48da --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptInjection.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import javax.script.Bindings; + +/** + * Inject custom settings on Script execution. + * + * @author Luca Garulli + * + */ +public interface OScriptInjection { + public void bind(Bindings binding); + + public void unbind(Bindings binding); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptManager.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptManager.java new file mode 100644 index 00000000000..9cd718cd2ba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptManager.java @@ -0,0 +1,372 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.common.concur.resource.OPartitionedObjectPool; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.parser.OStringParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.script.formatter.*; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.function.OFunctionUtilWrapper; +import com.orientechnologies.orient.core.sql.OSQLScriptEngine; +import com.orientechnologies.orient.core.sql.OSQLScriptEngineFactory; + +import javax.script.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Executes Script Commands. + * + * @author Luca Garulli + * @see OCommandScript + */ +public class OScriptManager { + protected static final Object[] EMPTY_PARAMS = new Object[] {}; + protected static final int LINES_AROUND_ERROR = 5; + protected final String DEF_LANGUAGE = "javascript"; + protected String defaultLanguage = DEF_LANGUAGE; + protected ScriptEngineManager scriptEngineManager; + protected Map engines = new HashMap(); + protected Map formatters = new HashMap(); + protected List injections = new ArrayList(); + protected ConcurrentHashMap dbManagers = new ConcurrentHashMap(); + + public OScriptManager() { + scriptEngineManager = new ScriptEngineManager(); + + registerEngine(OSQLScriptEngine.NAME, new OSQLScriptEngineFactory()); + + for (ScriptEngineFactory f : scriptEngineManager.getEngineFactories()) { + registerEngine(f.getLanguageName().toLowerCase(Locale.ENGLISH), f); + + if (defaultLanguage == null) + defaultLanguage = f.getLanguageName(); + } + + if (!existsEngine(DEF_LANGUAGE)) { + final ScriptEngine defEngine = scriptEngineManager.getEngineByName(DEF_LANGUAGE); + if (defEngine == null) { + OLogManager.instance().warn(this, "Cannot find default script language for %s", DEF_LANGUAGE); + } else { + // GET DIRECTLY THE LANGUAGE BY NAME (DON'T KNOW WHY SOMETIMES DOESN'T RETURN IT WITH getEngineFactories() ABOVE! + registerEngine(DEF_LANGUAGE, defEngine.getFactory()); + defaultLanguage = DEF_LANGUAGE; + } + } + + registerFormatter(OSQLScriptEngine.NAME, new OSQLScriptFormatter()); + registerFormatter(DEF_LANGUAGE, new OJSScriptFormatter()); + registerFormatter("ruby", new ORubyScriptFormatter()); + registerFormatter("groovy", new OGroovyScriptFormatter()); + } + + public String getFunctionDefinition(final OFunction iFunction) { + final OScriptFormatter formatter = formatters.get(iFunction.getLanguage().toLowerCase(Locale.ENGLISH)); + if (formatter == null) + throw new IllegalArgumentException("Cannot find script formatter for the language '" + iFunction.getLanguage() + "'"); + + return formatter.getFunctionDefinition(iFunction); + } + + public String getFunctionInvoke(final OFunction iFunction, final Object[] iArgs) { + final OScriptFormatter formatter = formatters.get(iFunction.getLanguage().toLowerCase(Locale.ENGLISH)); + if (formatter == null) + throw new IllegalArgumentException("Cannot find script formatter for the language '" + iFunction.getLanguage() + "'"); + + return formatter.getFunctionInvoke(iFunction, iArgs); + } + + /** + * Formats the library of functions for a language. + * + * @param db Current database instance + * @param iLanguage Language as filter + * + * @return String containing all the functions + */ + public String getLibrary(final ODatabase db, final String iLanguage) { + if (db == null) + // NO DB = NO LIBRARY + return null; + + final StringBuilder code = new StringBuilder(); + + final Set functions = db.getMetadata().getFunctionLibrary().getFunctionNames(); + for (String fName : functions) { + final OFunction f = db.getMetadata().getFunctionLibrary().getFunction(fName); + + if (f.getLanguage() == null) + throw new OConfigurationException("Database function '" + fName + "' has no language"); + + if (f.getLanguage().equalsIgnoreCase(iLanguage)) { + final String def = getFunctionDefinition(f); + if (def != null) { + code.append(def); + code.append("\n"); + } + } + } + + return code.length() == 0 ? null : code.toString(); + } + + public boolean existsEngine(String iLanguage) { + if (iLanguage == null) + return false; + + iLanguage = iLanguage.toLowerCase(Locale.ENGLISH); + return engines.containsKey(iLanguage); + } + + public ScriptEngine getEngine(final String iLanguage) { + if (iLanguage == null) + throw new OCommandScriptException("No language was specified"); + + final String lang = iLanguage.toLowerCase(Locale.ENGLISH); + + final ScriptEngineFactory scriptEngineFactory = engines.get(lang); + if (scriptEngineFactory == null) + throw new OCommandScriptException( + "Unsupported language: " + iLanguage + ". Supported languages are: " + getSupportedLanguages()); + + return scriptEngineFactory.getScriptEngine(); + } + + /** + * Acquires a database engine from the pool. Once finished using it, the instance MUST be returned in the pool by calling the + * method #releaseDatabaseEngine(String, ScriptEngine). + * + * @param databaseName Database name + * @param language Script language + * + * @return ScriptEngine instance with the function library already parsed + * + * @see #releaseDatabaseEngine(String, String, OPartitionedObjectPool.PoolEntry) + */ + public OPartitionedObjectPool.PoolEntry acquireDatabaseEngine(final String databaseName, final String language) { + ODatabaseScriptManager dbManager = dbManagers.get(databaseName); + if (dbManager == null) { + // CREATE A NEW DATABASE SCRIPT MANAGER + dbManager = new ODatabaseScriptManager(this, databaseName); + final ODatabaseScriptManager prev = dbManagers.putIfAbsent(databaseName, dbManager); + if (prev != null) { + dbManager.close(); + // GET PREVIOUS ONE + dbManager = prev; + } + } + + return dbManager.acquireEngine(language); + } + + /** + * Acquires a database engine from the pool. Once finished using it, the instance MUST be returned in the pool by calling the + * method + * + * @param iLanguage Script language + * @param iDatabaseName Database name + * @param poolEntry Pool entry to free + * + * @see #acquireDatabaseEngine(String, String) + */ + public void releaseDatabaseEngine(final String iLanguage, final String iDatabaseName, + final OPartitionedObjectPool.PoolEntry poolEntry) { + final ODatabaseScriptManager dbManager = dbManagers.get(iDatabaseName); + // We check if there is still a valid pool because it could be removed by the function reload + if (dbManager != null) { + dbManager.releaseEngine(iLanguage, poolEntry); + } + + } + + public Iterable getSupportedLanguages() { + final HashSet result = new HashSet(); + result.addAll(engines.keySet()); + return result; + } + + public Bindings bind(final Bindings binding, final ODatabaseDocumentTx db, final OCommandContext iContext, + final Map iArgs) { + if (db != null) { + // BIND FIXED VARIABLES + binding.put("db", new OScriptDocumentDatabaseWrapper(db)); + binding.put("orient", new OScriptOrientWrapper(db)); + } + binding.put("util", new OFunctionUtilWrapper()); + + for (OScriptInjection i : injections) + i.bind(binding); + + // BIND CONTEXT VARIABLE INTO THE SCRIPT + if (iContext != null) { + binding.put("ctx", iContext); + for (Entry a : iContext.getVariables().entrySet()) { + binding.put(a.getKey(), a.getValue()); + } + } + + // BIND PARAMETERS INTO THE SCRIPT + if (iArgs != null) { + for (Entry a : iArgs.entrySet()) { + binding.put(a.getKey().toString(), a.getValue()); + } + + binding.put("params", iArgs.values().toArray()); + } else + binding.put("params", EMPTY_PARAMS); + + return binding; + } + + public String throwErrorMessage(final ScriptException e, final String lib) { + int errorLineNumber = e.getLineNumber(); + + if (errorLineNumber <= 0) { + // FIX TO RHINO: SOMETIMES HAS THE LINE NUMBER INSIDE THE TEXT :-( + final String excMessage = e.toString(); + final int pos = excMessage.indexOf("#"); + if (pos > -1) { + final int end = excMessage.indexOf(')', pos + "#".length()); + String lineNumberAsString = excMessage.substring(pos + "#".length(), end); + errorLineNumber = Integer.parseInt(lineNumberAsString); + } + } + + if (errorLineNumber <= 0) { + throw new OCommandScriptException( + "Error on evaluation of the script library. Error: " + e.getMessage() + "\nScript library was:\n" + lib); + } else { + final StringBuilder code = new StringBuilder(); + final Scanner scanner = new Scanner(lib); + try { + scanner.useDelimiter("\n"); + String currentLine = null; + String lastFunctionName = "unknown"; + + for (int currentLineNumber = 1; scanner.hasNext(); currentLineNumber++) { + currentLine = scanner.next(); + int pos = currentLine.indexOf("function"); + if (pos > -1) { + final String[] words = OStringParser + .getWords(currentLine.substring(Math.min(pos + "function".length() + 1, currentLine.length())), " \r\n\t"); + if (words.length > 0 && "(".equals(words[0])) + lastFunctionName = words[0]; + } + + if (currentLineNumber == errorLineNumber) + // APPEND X LINES BEFORE + code.append(String.format("%4d: >>> %s\n", currentLineNumber, currentLine)); + else if (Math.abs(currentLineNumber - errorLineNumber) <= LINES_AROUND_ERROR) + // AROUND: APPEND IT + code.append(String.format("%4d: %s\n", currentLineNumber, currentLine)); + } + + code.insert(0, String.format("ScriptManager: error %s.\nFunction %s:\n\n", e.getMessage(), lastFunctionName)); + + } finally { + scanner.close(); + } + + throw new OCommandScriptException(code.toString()); + } + } + + @Deprecated + public void unbind(Bindings binding) { + unbind(binding, null, null); + } + + /** + * Unbinds variables + * + * @param binding + */ + public void unbind(final Bindings binding, final OCommandContext iContext, final Map iArgs) { + for (OScriptInjection i : injections) + i.unbind(binding); + + binding.put("db", null); + binding.put("orient", null); + + binding.put("util", null); + + binding.put("ctx", null); + if (iContext != null) { + for (Entry a : iContext.getVariables().entrySet()) + binding.put(a.getKey(), null); + } + + if (iArgs != null) { + for (Entry a : iArgs.entrySet()) + binding.put(a.getKey().toString(), null); + + } + binding.put("params", null); + } + + public void registerInjection(final OScriptInjection iInj) { + if (!injections.contains(iInj)) + injections.add(iInj); + } + + public void unregisterInjection(final OScriptInjection iInj) { + injections.remove(iInj); + } + + public List getInjections() { + return injections; + } + + public OScriptManager registerEngine(final String iLanguage, final ScriptEngineFactory iEngine) { + engines.put(iLanguage, iEngine); + return this; + } + + public OScriptManager registerFormatter(final String iLanguage, final OScriptFormatter iFormatterImpl) { + formatters.put(iLanguage.toLowerCase(Locale.ENGLISH), iFormatterImpl); + return this; + } + + /** + * Ask to the Script engine all the formatters + * + * @return Map containing all the formatters + */ + public Map getFormatters() { + return formatters; + } + + /** + * Closes the pool for a database. This is called at Orient shutdown and in case a function has been updated. + * + * @param iDatabaseName + */ + public void close(final String iDatabaseName) { + final ODatabaseScriptManager dbPool = dbManagers.remove(iDatabaseName); + if (dbPool != null) + dbPool.close(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptOrientWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptOrientWrapper.java new file mode 100644 index 00000000000..eaaf8808b91 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/OScriptOrientWrapper.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script; + +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.OConfigurationException; + +/** + * Orient wrapper class to use from scripts. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OScriptOrientWrapper { + protected final ODatabase db; + + public OScriptOrientWrapper() { + this.db = null; + } + + public OScriptOrientWrapper(final ODatabase db) { + this.db = db; + } + + public OScriptDocumentDatabaseWrapper getDatabase() { + if (db == null) + throw new OConfigurationException("No database instance found in context"); + + if (db instanceof ODatabaseDocumentTx) + return new OScriptDocumentDatabaseWrapper((ODatabaseDocumentTx) db); + + throw new OConfigurationException("No valid database instance found in context: " + db + ", class: " + db.getClass()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OGroovyScriptFormatter.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OGroovyScriptFormatter.java new file mode 100644 index 00000000000..b6fca709159 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OGroovyScriptFormatter.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script.formatter; + +import com.orientechnologies.orient.core.metadata.function.OFunction; + +/** + * Javascript script formatter. + * + * @author Luca Garulli + */ +public class OGroovyScriptFormatter implements OScriptFormatter { + public String getFunctionDefinition(final OFunction f) { + + final StringBuilder fCode = new StringBuilder(1024); + fCode.append("def "); + fCode.append(f.getName()); + fCode.append('('); + int i = 0; + if (f.getParameters() != null) + for (String p : f.getParameters()) { + if (i++ > 0) + fCode.append(','); + fCode.append(p); + } + fCode.append(") {\n"); + fCode.append(f.getCode()); + fCode.append("\n}\n"); + + return fCode.toString(); + } + + @Override + public String getFunctionInvoke(final OFunction iFunction, final Object[] iArgs) { + final StringBuilder code = new StringBuilder(1024); + + code.append(iFunction.getName()); + code.append('('); + if (iArgs != null) { + int i = 0; + for (Object a : iArgs) { + if (i++ > 0) + code.append(','); + code.append(a); + } + } + code.append(");"); + + return code.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OJSScriptFormatter.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OJSScriptFormatter.java new file mode 100644 index 00000000000..c9716832b1e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OJSScriptFormatter.java @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script.formatter; + +import com.orientechnologies.orient.core.metadata.function.OFunction; + +/** + * Javascript script formatter. + * + * @author Luca Garulli + * + */ +public class OJSScriptFormatter implements OScriptFormatter { + public String getFunctionDefinition(final OFunction f) { + + final StringBuilder fCode = new StringBuilder(1024); + fCode.append("function "); + fCode.append(f.getName()); + fCode.append('('); + int i = 0; + if (f.getParameters() != null) + for (String p : f.getParameters()) { + if (i++ > 0) + fCode.append(','); + fCode.append(p); + } + fCode.append(") {\n"); + fCode.append(f.getCode()); + fCode.append("\n}\n"); + + return fCode.toString(); + } + + @Override + public String getFunctionInvoke(final OFunction iFunction, final Object[] iArgs) { + final StringBuilder code = new StringBuilder(1024); + + code.append(iFunction.getName()); + code.append('('); + if (iArgs != null) { + int i = 0; + for (Object a : iArgs) { + if (i++ > 0) + code.append(','); + code.append(a); + } + } + code.append(");"); + + return code.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/ORubyScriptFormatter.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/ORubyScriptFormatter.java new file mode 100644 index 00000000000..fbff7aa4bc4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/ORubyScriptFormatter.java @@ -0,0 +1,82 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script.formatter; + +import com.orientechnologies.orient.core.metadata.function.OFunction; + +import java.util.Scanner; + +/** + * Ruby script formatter + * + * @author Luca Garulli + * + */ +public class ORubyScriptFormatter implements OScriptFormatter { + public String getFunctionDefinition(final OFunction f) { + + final StringBuilder fCode = new StringBuilder(1024); + fCode.append("def "); + fCode.append(f.getName()); + fCode.append('('); + int i = 0; + if (f.getParameters() != null) + for (String p : f.getParameters()) { + if (i++ > 0) + fCode.append(','); + fCode.append(p); + } + fCode.append(")\n"); + + final Scanner scanner = new Scanner(f.getCode()); + try { + scanner.useDelimiter("\n").skip("\r"); + + while (scanner.hasNext()) { + fCode.append('\t'); + fCode.append(scanner.next()); + } + } finally { + scanner.close(); + } + fCode.append("\nend\n"); + + return fCode.toString(); + } + + @Override + public String getFunctionInvoke(final OFunction iFunction, final Object[] iArgs) { + final StringBuilder code = new StringBuilder(1024); + + code.append(iFunction.getName()); + code.append('('); + if (iArgs != null) { + int i = 0; + for (Object a : iArgs) { + if (i++ > 0) + code.append(','); + code.append(a); + } + } + code.append(");"); + + return code.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OSQLScriptFormatter.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OSQLScriptFormatter.java new file mode 100644 index 00000000000..5dc168cb6e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OSQLScriptFormatter.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.script.formatter; + +import com.orientechnologies.orient.core.metadata.function.OFunction; + +/** + * SQL script formatter. + * + * @author Luca Garulli + * + */ +public class OSQLScriptFormatter implements OScriptFormatter { + public String getFunctionDefinition(final OFunction f) { + return null; + } + + @Override + public String getFunctionInvoke(final OFunction iFunction, final Object[] iArgs) { + // TODO: BIND ARGS + return iFunction.getCode(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OScriptFormatter.java b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OScriptFormatter.java new file mode 100644 index 00000000000..995a5f7af0a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/script/formatter/OScriptFormatter.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.command.script.formatter; + +import com.orientechnologies.orient.core.metadata.function.OFunction; + +/** + * Interface to provide script formatter in any language. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OScriptFormatter { + public String getFunctionDefinition(OFunction iFunction); + + public String getFunctionInvoke(OFunction iFunction, final Object[] iArgs); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverse.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverse.java new file mode 100644 index 00000000000..3f0361338df --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverse.java @@ -0,0 +1,222 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.orient.core.command.OCommand; +import com.orientechnologies.orient.core.command.OCommandExecutorAbstract; +import com.orientechnologies.orient.core.command.OCommandPredicate; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Base class for traversing. + * + * @author Luca Garulli + */ +public class OTraverse implements OCommand, Iterable, Iterator { + private OCommandPredicate predicate; + private Iterator target; + private List fields = new ArrayList(); + private long resultCount = 0; + private long limit = 0; + private OIdentifiable lastTraversed; + private STRATEGY strategy = STRATEGY.DEPTH_FIRST; + private OTraverseContext context = new OTraverseContext(); + private int maxDepth = -1; + + public enum STRATEGY { + DEPTH_FIRST, BREADTH_FIRST + } + + /* + * Executes a traverse collecting all the result in the returning List. This could be memory expensive because for + * large results the list could be huge. it's always better to use it as an Iterable and lazy fetch each result on next() call. + * + * @see com.orientechnologies.orient.core.command.OCommand#execute() + */ + public List execute() { + final List result = new ArrayList(); + while (hasNext()) + result.add(next()); + return result; + } + + public OTraverseAbstractProcess nextProcess() { + return context.next(); + } + + public boolean hasNext() { + if (limit > 0 && resultCount >= limit) + return false; + + if (lastTraversed == null) + // GET THE NEXT + lastTraversed = next(); + + if (lastTraversed == null && !context.isEmpty()) + throw new IllegalStateException("Traverse ended abnormally"); + + if (!OCommandExecutorAbstract.checkInterruption(context)) + return false; + + // BROWSE ALL THE RECORDS + return lastTraversed != null; + } + + public OIdentifiable next() { + if (Thread.interrupted()) + throw new OCommandExecutionException("The traverse execution has been interrupted"); + + if (lastTraversed != null) { + // RETURN LATEST AND RESET IT + final OIdentifiable result = lastTraversed; + lastTraversed = null; + return result; + } + + if (limit > 0 && resultCount >= limit) + return null; + + OIdentifiable result; + OTraverseAbstractProcess toProcess; + // RESUME THE LAST PROCESS + while ((toProcess = nextProcess()) != null) { + result = toProcess.process(); + if (result != null) { + resultCount++; + return result; + } + } + + return null; + } + + public void remove() { + throw new UnsupportedOperationException("remove()"); + } + + public Iterator iterator() { + return this; + } + + public OTraverseContext getContext() { + return context; + } + + public OTraverse target(final Iterable iTarget) { + return target(iTarget.iterator()); + } + + public OTraverse target(final OIdentifiable... iRecords) { + final List list = new ArrayList(); + Collections.addAll(list, iRecords); + return target(list.iterator()); + } + + @SuppressWarnings("unchecked") + public OTraverse target(final Iterator iTarget) { + target = iTarget; + context.reset(); + new OTraverseRecordSetProcess(this, (Iterator) target, OTraversePath.empty()); + return this; + } + + public Iterator getTarget() { + return target; + } + + public OTraverse predicate(final OCommandPredicate iPredicate) { + predicate = iPredicate; + return this; + } + + public OCommandPredicate getPredicate() { + return predicate; + } + + public OTraverse field(final Object iField) { + if (!fields.contains(iField)) + fields.add(iField); + return this; + } + + public OTraverse fields(final Collection iFields) { + for (Object f : iFields) + field(f); + return this; + } + + public OTraverse fields(final String... iFields) { + for (String f : iFields) + field(f); + return this; + } + + public List getFields() { + return fields; + } + + public long getLimit() { + return limit; + } + + public OTraverse limit(final long iLimit) { + if (iLimit < -1) + throw new IllegalArgumentException("Limit cannot be negative. 0 = infinite"); + this.limit = iLimit; + return this; + } + + @Override + public String toString() { + return String.format("OTraverse.target(%s).fields(%s).limit(%d).predicate(%s)", target, fields, limit, predicate); + } + + public long getResultCount() { + return resultCount; + } + + public OIdentifiable getLastTraversed() { + return lastTraversed; + } + + public STRATEGY getStrategy() { + return strategy; + } + + public void setStrategy(STRATEGY strategy) { + this.strategy = strategy; + context.setStrategy(strategy); + } + + public int getMaxDepth() { + return maxDepth; + } + + public void setMaxDepth(final int maxDepth) { + this.maxDepth = maxDepth; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseAbstractProcess.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseAbstractProcess.java new file mode 100644 index 00000000000..fd5c2d64e9c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseAbstractProcess.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.orient.core.command.OCommandProcess; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +public abstract class OTraverseAbstractProcess extends OCommandProcess { + public OTraverseAbstractProcess(final OTraverse iCommand, final T iTarget) { + super(iCommand, iTarget); + } + + public OIdentifiable pop() { + command.getContext().pop(null); + return null; + } + + public abstract OTraversePath getPath(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseContext.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseContext.java new file mode 100644 index 00000000000..7bfdf2a86d5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseContext.java @@ -0,0 +1,224 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OBasicCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; + +import java.util.*; + +public class OTraverseContext extends OBasicCommandContext { + private Memory memory = new StackMemory(); + private Set history = new HashSet(); + + private OTraverseAbstractProcess currentProcess; + + public void push(final OTraverseAbstractProcess iProcess) { + memory.add(iProcess); + } + + public Map getVariables() { + final HashMap map = new HashMap(); + map.put("depth", getDepth()); + map.put("path", getPath()); + map.put("stack", memory.getUnderlying()); + // DELEGATE + map.putAll(super.getVariables()); + return map; + } + + public Object getVariable(final String iName) { + final String name = iName.trim().toUpperCase(Locale.ENGLISH); + + if ("DEPTH".startsWith(name)) + return getDepth(); + else if (name.startsWith("PATH")) + return ODocumentHelper.getFieldValue(getPath(), iName.substring("PATH".length())); + else if (name.startsWith("STACK")) { + + Object result = ODocumentHelper.getFieldValue(memory.getUnderlying(), iName.substring("STACK".length())); + if (result instanceof ArrayDeque) { + result = ((ArrayDeque) result).clone(); + } + return result; + } else if (name.startsWith("HISTORY")) + return ODocumentHelper.getFieldValue(history, iName.substring("HISTORY".length())); + else + // DELEGATE + return super.getVariable(iName); + } + + public void pop(final OIdentifiable currentRecord) { + if (currentRecord != null) { + final ORID rid = currentRecord.getIdentity(); + if (!history.remove(rid)) + OLogManager.instance().warn(this, "Element '" + rid + "' not found in traverse history"); + } + + try { + memory.dropFrame(); + } catch (NoSuchElementException e) { + throw new IllegalStateException("Traverse stack is empty", e); + } + } + + public OTraverseAbstractProcess next() { + currentProcess = memory.next(); + return currentProcess; + } + + public boolean isEmpty() { + return memory.isEmpty(); + } + + public void reset() { + memory.clear(); + } + + public boolean isAlreadyTraversed(final OIdentifiable identity, final int iLevel) { + if (history.contains(identity.getIdentity())) + return true; + + // final int[] l = history.get(identity.getIdentity()); + // if (l == null) + // return false; + // + // for (int i = 0; i < l.length && l[i] > -1; ++i) + // if (l[i] == iLevel) + // return true; + + return false; + } + + public void addTraversed(final OIdentifiable identity, final int iLevel) { + history.add(identity.getIdentity()); + + // final int[] l = history.get(identity.getIdentity()); + // if (l == null) { + // final int[] array = new int[BUCKET_SIZE]; + // array[0] = iLevel; + // Arrays.fill(array, 1, BUCKET_SIZE, -1); + // history.put(identity.getIdentity(), array); + // } else { + // if (l[l.length - 1] > -1) { + // // ARRAY FULL, ENLARGE IT + // final int[] array = Arrays.copyOf(l, l.length + BUCKET_SIZE); + // array[l.length] = iLevel; + // Arrays.fill(array, l.length + 1, array.length, -1); + // history.put(identity.getIdentity(), array); + // } else { + // for (int i = l.length - 2; i >= 0; --i) { + // if (l[i] > -1) { + // l[i + 1] = iLevel; + // break; + // } + // } + // } + // } + } + + public String getPath() { + return currentProcess == null ? "" : currentProcess.getPath().toString(); + } + + public int getDepth() { + return currentProcess == null ? 0 : currentProcess.getPath().getDepth(); + } + + public void setStrategy(final OTraverse.STRATEGY strategy) { + if (strategy == OTraverse.STRATEGY.BREADTH_FIRST) + memory = new QueueMemory(memory); + else + memory = new StackMemory(memory); + } + + private interface Memory { + void add(OTraverseAbstractProcess iProcess); + + OTraverseAbstractProcess next(); + + void dropFrame(); + + void clear(); + + Collection> getUnderlying(); + + boolean isEmpty(); + } + + private abstract static class AbstractMemory implements Memory { + protected final Deque> deque; + + public AbstractMemory() { + deque = new ArrayDeque>(); + } + + public AbstractMemory(final Memory memory) { + deque = new ArrayDeque>(memory.getUnderlying()); + } + + @Override public OTraverseAbstractProcess next() { + return deque.peek(); + } + + @Override public void dropFrame() { + deque.removeFirst(); + } + + @Override public void clear() { + deque.clear(); + } + + @Override public boolean isEmpty() { + return deque.isEmpty(); + } + + @Override public Collection> getUnderlying() { + return deque; + } + } + + private static class StackMemory extends AbstractMemory { + public StackMemory() { + super(); + } + + public StackMemory(final Memory memory) { + super(memory); + } + + @Override public void add(final OTraverseAbstractProcess iProcess) { + deque.push(iProcess); + } + } + + private static class QueueMemory extends AbstractMemory { + public QueueMemory(final Memory memory) { + super(memory); + } + + @Override public void add(final OTraverseAbstractProcess iProcess) { + deque.addLast(iProcess); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseMultiValueProcess.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseMultiValueProcess.java new file mode 100644 index 00000000000..41fb0780c3c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseMultiValueProcess.java @@ -0,0 +1,72 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.orient.core.db.record.OAutoConvertToRecord; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; + +import java.util.Iterator; + +public class OTraverseMultiValueProcess extends OTraverseAbstractProcess> { + private final OTraversePath parentPath; + protected Object value; + protected int index = -1; + + public OTraverseMultiValueProcess(final OTraverse iCommand, final Iterator iTarget, OTraversePath parentPath) { + super(iCommand, iTarget); + this.parentPath = parentPath; + + if (target instanceof OAutoConvertToRecord) + // FORCE AVOIDING TO CONVERT IN RECORD + ((OAutoConvertToRecord) target).setAutoConvertToRecord(false); + } + + public OIdentifiable process() { + while (target.hasNext()) { + value = target.next(); + index++; + + if (value instanceof OIdentifiable) { + + if (value instanceof ORID) { + value = ((OIdentifiable) value).getRecord(); + } + final OTraverseAbstractProcess subProcess = new OTraverseRecordProcess(command, (OIdentifiable) value, + getPath()); + command.getContext().push(subProcess); + + return null; + } + } + + return pop(); + } + + @Override + public OTraversePath getPath() { + return parentPath.appendIndex(index); + } + + @Override + public String toString() { + return "[idx:" + index + "]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraversePath.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraversePath.java new file mode 100644 index 00000000000..aa27bb3f062 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraversePath.java @@ -0,0 +1,142 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayDeque; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OTraversePath { + private static final OTraversePath EMPTY_PATH = new OTraversePath(new FirstPathItem()); + + private final PathItem lastPathItem; + + private OTraversePath(PathItem lastPathItem) { + this.lastPathItem = lastPathItem; + } + + @Override + public String toString() { + final ArrayDeque stack = new ArrayDeque(); + PathItem currentItem = lastPathItem; + while (currentItem != null) { + stack.push(currentItem); + currentItem = currentItem.parentItem; + } + + final StringBuilder buf = new StringBuilder(1024); + for (PathItem pathItem : stack) { + buf.append(pathItem.toString()); + } + + return buf.toString(); + } + + public OTraversePath append(OIdentifiable record) { + return new OTraversePath(new RecordPathItem(record, lastPathItem)); + } + + public OTraversePath appendField(String fieldName) { + return new OTraversePath(new FieldPathItem(fieldName, lastPathItem)); + } + + public OTraversePath appendIndex(int index) { + return new OTraversePath(new CollectionPathItem(index, lastPathItem)); + } + + public OTraversePath appendRecordSet() { + return this; + } + + public int getDepth() { + return lastPathItem.depth; + } + + public static OTraversePath empty() { + return EMPTY_PATH; + } + + private static abstract class PathItem { + protected final PathItem parentItem; + protected final int depth; + + private PathItem(PathItem parentItem, int depth) { + this.parentItem = parentItem; + this.depth = depth; + } + } + + private static class RecordPathItem extends PathItem { + private final OIdentifiable record; + + private RecordPathItem(OIdentifiable record, PathItem parentItem) { + super(parentItem, parentItem.depth + 1); + this.record = record; + } + + @Override + public String toString() { + return "(" + record.getIdentity().toString() + ")"; + } + } + + private static class FieldPathItem extends PathItem { + private final String name; + + private FieldPathItem(String name, PathItem parentItem) { + super(parentItem, parentItem.depth); + this.name = name; + } + + @Override + public String toString() { + return "." + name; + } + } + + private static class CollectionPathItem extends PathItem { + private final int index; + + private CollectionPathItem(int index, PathItem parentItem) { + super(parentItem, parentItem.depth); + this.index = index; + } + + @Override + public String toString() { + return "[" + index + "]"; + } + } + + private static class FirstPathItem extends PathItem { + private FirstPathItem() { + super(null, -1); + } + + @Override + public String toString() { + return ""; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordProcess.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordProcess.java new file mode 100644 index 00000000000..ce48d9c5e84 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordProcess.java @@ -0,0 +1,183 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemFieldAll; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemFieldAny; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class OTraverseRecordProcess extends OTraverseAbstractProcess { + private final OTraversePath path; + + public OTraverseRecordProcess(final OTraverse iCommand, final OIdentifiable iTarget, OTraversePath parentPath) { + super(iCommand, iTarget); + this.path = parentPath.append(iTarget); + } + + public OIdentifiable process() { + if (target == null) + return pop(); + + final int depth = path.getDepth(); + + if (command.getContext().isAlreadyTraversed(target, depth)) + // ALREADY EVALUATED, DON'T GO IN DEEP + return drop(); + + if (command.getPredicate() != null) { + final Object conditionResult = command.getPredicate().evaluate(target, null, command.getContext()); + if (conditionResult != Boolean.TRUE) + return drop(); + } + + // UPDATE ALL TRAVERSED RECORD TO AVOID RECURSION + command.getContext().addTraversed(target, depth); + + final int maxDepth = command.getMaxDepth(); + if (maxDepth > -1 && depth == maxDepth) { + // SKIP IT + pop(); + } else { + final ORecord targetRec = target.getRecord(); + if (!(targetRec instanceof ODocument)) + // SKIP IT + return pop(); + + final ODocument targetDoc = (ODocument) targetRec; + + // MATCH! + final List fields = new ArrayList(); + + // TRAVERSE THE DOCUMENT ITSELF + for (Object cfgFieldObject : command.getFields()) { + String cfgField = cfgFieldObject.toString(); + + if ("*".equals(cfgField) || OSQLFilterItemFieldAll.FULL_NAME.equalsIgnoreCase(cfgField) + || OSQLFilterItemFieldAny.FULL_NAME.equalsIgnoreCase(cfgField)) { + + // ADD ALL THE DOCUMENT FIELD + Collections.addAll(fields, targetDoc.fieldNames()); + break; + + } else { + // SINGLE FIELD + final int pos = OStringSerializerHelper + .parse(cfgField, new StringBuilder(), 0, -1, new char[] { '.' }, true, true, true, 0, true) - 1; + if (pos > -1) { + // FOUND . + final OClass cls = ODocumentInternal.getImmutableSchemaClass(targetDoc); + if (cls == null) + // JUMP IT BECAUSE NO SCHEMA + continue; + + final String className = cfgField.substring(0, pos); + if (!cls.isSubClassOf(className)) + // JUMP IT BECAUSE IT'S NOT A INSTANCEOF THE CLASS + continue; + + cfgField = cfgField.substring(pos + 1); + + fields.add(cfgField); + } else + fields.add(cfgFieldObject); + } + } + + if (command.getStrategy() == OTraverse.STRATEGY.DEPTH_FIRST) + // REVERSE NAMES TO BE PROCESSED IN THE RIGHT ORDER + Collections.reverse(fields); + + processFields(fields.iterator()); + + if (targetDoc.isEmbedded()) + return null; + } + + return target; + } + + private void processFields(Iterator target) { + final ODocument doc = this.target.getRecord(); + + while (target.hasNext()) { + Object field = target.next(); + + final Object fieldValue; + if (field instanceof OSQLFilterItem) + fieldValue = ((OSQLFilterItem) field).getValue(doc, null, null); + else + fieldValue = doc.rawField(field.toString()); + + if (fieldValue != null) { + final OTraverseAbstractProcess subProcess; + + if (fieldValue instanceof Iterator || OMultiValue.isMultiValue(fieldValue)) { + final Iterator coll; + if (fieldValue instanceof ORecordLazyMultiValue) + coll = ((ORecordLazyMultiValue) fieldValue).rawIterator(); + else + coll = OMultiValue.getMultiValueIterator(fieldValue, false); + + subProcess = new OTraverseMultiValueProcess(command, (Iterator) coll, getPath().appendField(field.toString())); + } else if (fieldValue instanceof OIdentifiable && ((OIdentifiable) fieldValue).getRecord() instanceof ODocument) { + subProcess = new OTraverseRecordProcess(command, (ODocument) ((OIdentifiable) fieldValue).getRecord(), getPath() + .appendField(field.toString())); + } else + continue; + + command.getContext().push(subProcess); + } + } + } + + @Override + public String toString() { + return target != null ? target.getIdentity().toString() : "-"; + } + + @Override + public OTraversePath getPath() { + return path; + } + + public OIdentifiable drop() { + command.getContext().pop(null); + return null; + } + + @Override + public OIdentifiable pop() { + command.getContext().pop(target); + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordSetProcess.java b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordSetProcess.java new file mode 100644 index 00000000000..183bef58e6f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/command/traverse/OTraverseRecordSetProcess.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.command.traverse; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Iterator; + +public class OTraverseRecordSetProcess extends OTraverseAbstractProcess> { + private final OTraversePath path; + protected OIdentifiable record; + protected int index = -1; + + public OTraverseRecordSetProcess(final OTraverse iCommand, final Iterator iTarget, OTraversePath parentPath) { + super(iCommand, iTarget); + this.path = parentPath.appendRecordSet(); + command.getContext().push(this); + } + + @SuppressWarnings("unchecked") + public OIdentifiable process() { + while (target.hasNext()) { + record = target.next(); + index++; + + final ORecord rec = record.getRecord(); + if (rec instanceof ODocument) { + ODocument doc = (ODocument) rec; + if (doc.getIdentity().getClusterId() == -2 && doc.fields() == 1) { // projection + // EXTRACT THE FIELD CONTEXT + Object fieldvalue = doc.field(doc.fieldNames()[0]); + if (fieldvalue instanceof Collection) { + command.getContext() + .push(new OTraverseRecordSetProcess(command, ((Collection) fieldvalue).iterator(), getPath())); + + } else if (fieldvalue instanceof ODocument) { + command.getContext().push(new OTraverseRecordProcess(command, (ODocument) fieldvalue, getPath())); + } + } else { + command.getContext().push(new OTraverseRecordProcess(command, (ODocument) rec, getPath())); + } + + return null; + } + } + + return pop(); + } + + @Override + public OTraversePath getPath() { + return path; + } + + @Override + public String toString() { + return target != null ? target.toString() : "-"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/OCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/OCompression.java new file mode 100755 index 00000000000..1e76c21d9e0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/OCompression.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression; + +/** + * /** Storage compression interface. Additional compression implementations can be plugged via register() method. + * There are 2 versions:
      + *
        + *
      • OCompressionFactory.INSTANCE.register() for stateful implementations, a new instance will be created for + * each storage/li> + *
      • OCompressionFactory.INSTANCE.register() for stateless implementations, the same instance will be + * shared across all the storages./li> + *
      + * + * @author Andrey Lomakin + * @author Luca Garulli + * @since 05.06.13 + */ +public interface OCompression { + byte[] compress(byte[] content); + + byte[] compress(byte[] content, final int offset, final int length); + + byte[] uncompress(byte[] content); + + byte[] uncompress(byte[] content, final int offset, final int length); + + String name(); + + OCompression configure(String iOptions); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/OCompressionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/compression/OCompressionFactory.java new file mode 100755 index 00000000000..df9152ac81c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/OCompressionFactory.java @@ -0,0 +1,132 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.compression.impl.OGZIPCompression; +import com.orientechnologies.orient.core.compression.impl.OHighZIPCompression; +import com.orientechnologies.orient.core.compression.impl.OLowZIPCompression; +import com.orientechnologies.orient.core.compression.impl.ONothingCompression; +import com.orientechnologies.orient.core.compression.impl.OSnappyCompression; +import com.orientechnologies.orient.core.exception.OSecurityException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Factory of compression algorithms. + * + * @author Andrey Lomakin + * @since 05.06.13 + */ +public class OCompressionFactory { + public static final OCompressionFactory INSTANCE = new OCompressionFactory(); + + private final Map compressions = new HashMap(); + private final Map> compressionClasses = new HashMap>(); + + /** + * Install default compression algorithms. + */ + public OCompressionFactory() { + register(new OHighZIPCompression()); + register(new OLowZIPCompression()); + register(new OGZIPCompression()); + register(new OSnappyCompression()); + register(new ONothingCompression()); + } + + public OCompression getCompression(final String name, final String iOptions) { + OCompression compression = compressions.get(name); + if (compression == null) { + + final Class compressionClass; + if (name == null) + compressionClass = ONothingCompression.class; + else + compressionClass = compressionClasses.get(name); + + if (compressionClass != null) { + try { + compression = compressionClass.newInstance(); + compression.configure(iOptions); + + } catch (Exception e) { + throw OException.wrapException(new OSecurityException("Cannot instantiate compression algorithm '" + name + "'"), e); + } + } else + throw new OSecurityException("Compression with name '" + name + "' is absent"); + } + return compression; + } + + /** + * Registers a stateful implementations, a new instance will be created for each storage. + * + * @param compression + * Compression instance + */ + public void register(final OCompression compression) { + try { + final String name = compression.name(); + + if (compressions.containsKey(name)) + throw new IllegalArgumentException("Compression with name '" + name + "' was already registered"); + + if (compressionClasses.containsKey(name)) + throw new IllegalArgumentException("Compression with name '" + name + "' was already registered"); + + compressions.put(name, compression); + } catch (Exception e) { + OLogManager.instance().error(this, "Cannot register storage compression algorithm '%s'", e, compression); + } + } + + /** + * Registers a stateless implementations, the same instance will be shared on all the storages. + * + * @param compression + * Compression class + */ + public void register(final Class compression) { + try { + final OCompression tempInstance = compression.newInstance(); + + final String name = tempInstance.name(); + + if (compressions.containsKey(name)) + throw new IllegalArgumentException("Compression with name '" + name + "' was already registered"); + + if (compressionClasses.containsKey(tempInstance.name())) + throw new IllegalArgumentException("Compression with name '" + name + "' was already registered"); + + compressionClasses.put(name, compression); + } catch (Exception e) { + OLogManager.instance().error(this, "Cannot register storage compression algorithm '%s'", e, compression); + } + } + + public Set getCompressions() { + return compressions.keySet(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OAbstractCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OAbstractCompression.java new file mode 100755 index 00000000000..08c8114e59f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OAbstractCompression.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import com.orientechnologies.orient.core.compression.OCompression; + +/** + * Base class for the compression implementations. + * + * @author Luca Garulli + * @since 05.06.13 + */ +public abstract class OAbstractCompression implements OCompression { + @Override + public byte[] compress(final byte[] content) { + return compress(content, 0, content.length); + } + + @Override + public byte[] uncompress(final byte[] content) { + return uncompress(content, 0, content.length); + } + + @Override + public OCompression configure(final String iOptions) { + return this; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OGZIPCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OGZIPCompression.java new file mode 100755 index 00000000000..a090345d082 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OGZIPCompression.java @@ -0,0 +1,103 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import com.orientechnologies.orient.core.serialization.OMemoryInputStream; +import com.orientechnologies.orient.core.serialization.OMemoryStream; + +import java.io.IOException; +import java.util.Arrays; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * @author Andrey Lomakin + * @since 05.06.13 + */ +public class OGZIPCompression extends OAbstractCompression { + public static final String NAME = "gzip"; + + public static final OGZIPCompression INSTANCE = new OGZIPCompression(); + + @Override + public byte[] compress(final byte[] content, final int offset, final int length) { + try { + final byte[] result; + final OMemoryStream memoryOutputStream = new OMemoryStream(); + final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(memoryOutputStream, 16384); // 16KB + try { + gzipOutputStream.write(content, offset, length); + gzipOutputStream.finish(); + result = memoryOutputStream.toByteArray(); + } finally { + gzipOutputStream.close(); + } + + return result; + } catch (IOException ioe) { + throw new IllegalStateException("Exception during data compression", ioe); + } + } + + @Override + public byte[] uncompress(byte[] content, final int offset, final int length) { + try { + final OMemoryInputStream memoryInputStream = new OMemoryInputStream(content, offset, length); + final GZIPInputStream gzipInputStream = new GZIPInputStream(memoryInputStream, 16384); // 16KB + + try { + final byte[] buffer = new byte[1024]; + byte[] result = new byte[1024]; + + int bytesRead; + + int len = 0; + while ((bytesRead = gzipInputStream.read(buffer, 0, buffer.length)) > -1) { + if (len + bytesRead > result.length) { + int newSize = 2 * result.length; + if (newSize < len + bytesRead) + newSize = Integer.MAX_VALUE; + + final byte[] oldResult = result; + result = new byte[newSize]; + System.arraycopy(oldResult, 0, result, 0, oldResult.length); + } + + System.arraycopy(buffer, 0, result, len, bytesRead); + len += bytesRead; + } + + return Arrays.copyOf(result, len); + + } finally { + gzipInputStream.close(); + } + + } catch (IOException ioe) { + throw new IllegalStateException("Exception during data uncompression", ioe); + } + } + + @Override + public String name() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OHighZIPCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OHighZIPCompression.java new file mode 100755 index 00000000000..24563b46bbc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OHighZIPCompression.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import java.util.zip.ZipOutputStream; + +/** + * Compression implementation that use ZIP algorithm to the maximum level of compression + * + * @author Luca Garulli + */ +public class OHighZIPCompression extends OZIPCompression { + public static final OHighZIPCompression INSTANCE = new OHighZIPCompression(); + public static final String NAME = "high-zip"; + + @Override + public String name() { + return NAME; + } + + protected void setLevel(ZipOutputStream zipOutputStream) { + zipOutputStream.setLevel(9); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OLowZIPCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OLowZIPCompression.java new file mode 100755 index 00000000000..b852b0f8f09 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OLowZIPCompression.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import java.util.zip.ZipOutputStream; + +/** + * Compression implementation that use ZIP algorithm to the maximum level of compression + * + * @author Luca Garulli + */ +public class OLowZIPCompression extends OZIPCompression { + public static final OLowZIPCompression INSTANCE = new OLowZIPCompression(); + public static final String NAME = "low-zip"; + + @Override + public String name() { + return NAME; + } + + protected void setLevel(ZipOutputStream zipOutputStream) { + zipOutputStream.setLevel(1); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/ONothingCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/ONothingCompression.java new file mode 100755 index 00000000000..78a2df0ac6f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/ONothingCompression.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +/** + * @author Andrey Lomakin + * @since 05.06.13 + */ +public class ONothingCompression extends OAbstractCompression { + public static final String NAME = "nothing"; + + public static final ONothingCompression INSTANCE = new ONothingCompression(); + + @Override + public byte[] compress(final byte[] content, final int offset, final int length) { + if (offset == 0 && length == content.length) + return content; + + byte[] result = new byte[length]; + System.arraycopy(content, offset, result, 0, length); + + return result; + } + + @Override + public byte[] uncompress(final byte[] content, final int offset, final int length) { + if (offset == 0 && length == content.length) + return content; + + byte[] result = new byte[length]; + System.arraycopy(content, offset, result, 0, length); + + return result; + } + + @Override + public String name() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OSnappyCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OSnappyCompression.java new file mode 100755 index 00000000000..99f294d6d07 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OSnappyCompression.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import org.xerial.snappy.Snappy; + +import java.io.IOException; + +/** + * @author Andrey Lomakin + * @since 05.06.13 + */ +public class OSnappyCompression extends OAbstractCompression { + public static final String NAME = "snappy"; + + public static final OSnappyCompression INSTANCE = new OSnappyCompression(); + + @Override + public byte[] compress(byte[] content, final int offset, final int length) { + try { + final byte[] buf = new byte[Snappy.maxCompressedLength(length)]; + final int compressedByteSize = Snappy.rawCompress(content, offset, length, buf, 0); + final byte[] result = new byte[compressedByteSize]; + System.arraycopy(buf, 0, result, 0, compressedByteSize); + return result; + } catch (IOException e) { + throw OException.wrapException(new ODatabaseException("Error during data compression"), e); + } + } + + @Override + public byte[] uncompress(byte[] content, final int offset, final int length) { + try { + byte[] result = new byte[Snappy.uncompressedLength(content, offset, length)]; + int byteSize = Snappy.uncompress(content, offset, length, result, 0); + return result; + + } catch (IOException e) { + throw OException.wrapException(new ODatabaseException("Error during data decompression"), e); + } + } + + @Override + public String name() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompression.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompression.java new file mode 100755 index 00000000000..966612d24ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompression.java @@ -0,0 +1,107 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import com.orientechnologies.orient.core.serialization.OMemoryInputStream; +import com.orientechnologies.orient.core.serialization.OMemoryStream; + +import java.io.IOException; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * @author Luca Garulli + */ +public abstract class OZIPCompression extends OAbstractCompression { + @Override + public byte[] compress(final byte[] content, final int offset, final int length) { + try { + final byte[] result; + final OMemoryStream memoryOutputStream = new OMemoryStream(); + final ZipOutputStream zipOutputStream = new ZipOutputStream(memoryOutputStream); + setLevel(zipOutputStream); + + try { + ZipEntry ze = new ZipEntry("content"); + zipOutputStream.putNextEntry(ze); + try { + zipOutputStream.write(content, offset, length); + } finally { + zipOutputStream.closeEntry(); + } + + zipOutputStream.finish(); + result = memoryOutputStream.toByteArray(); + } finally { + zipOutputStream.close(); + } + + return result; + } catch (IOException ioe) { + throw new IllegalStateException("Exception during data compression", ioe); + } + } + + @Override + public byte[] uncompress(final byte[] content, final int offset, final int length) { + try { + final OMemoryInputStream memoryInputStream = new OMemoryInputStream(content, offset, length); + final ZipInputStream gzipInputStream = new ZipInputStream(memoryInputStream); // 16KB + + try { + final byte[] buffer = new byte[1024]; + byte[] result = new byte[1024]; + + int bytesRead; + + ZipEntry entry = gzipInputStream.getNextEntry(); + + int len = 0; + while ((bytesRead = gzipInputStream.read(buffer, 0, buffer.length)) > -1) { + if (len + bytesRead > result.length) { + int newSize = 2 * result.length; + if (newSize < len + bytesRead) + newSize = Integer.MAX_VALUE; + + final byte[] oldResult = result; + result = new byte[newSize]; + System.arraycopy(oldResult, 0, result, 0, oldResult.length); + } + + System.arraycopy(buffer, 0, result, len, bytesRead); + len += bytesRead; + } + + return Arrays.copyOf(result, len); + + } finally { + gzipInputStream.close(); + } + + } catch (IOException ioe) { + throw new IllegalStateException("Exception during data uncompression", ioe); + } + } + + protected abstract void setLevel(ZipOutputStream zipOutputStream); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompressionUtil.java b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompressionUtil.java new file mode 100644 index 00000000000..271a2edfff4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/compression/impl/OZIPCompressionUtil.java @@ -0,0 +1,207 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.compression.impl; + +import com.orientechnologies.common.io.OFileUtils; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandOutputListener; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Compression Utility. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OZIPCompressionUtil { + public static List compressDirectory(final String sourceFolderName, final OutputStream output, + final String[] iSkipFileExtensions, final OCommandOutputListener iOutput, int compressionLevel) throws IOException { + + final List compressedFiles = new ArrayList(); + + final ZipOutputStream zos = new ZipOutputStream(output); + zos.setComment("OrientDB Backup executed on " + new Date()); + try { + zos.setLevel(compressionLevel); + addFolder(zos, sourceFolderName, sourceFolderName, iSkipFileExtensions, iOutput, compressedFiles); + + return compressedFiles; + } finally { + zos.close(); + } + } + + /*** + * Extract zipfile to outdir with complete directory structure + */ + public static void uncompressDirectory(final InputStream in, final String out, final OCommandOutputListener iListener) + throws IOException { + final File outdir = new File(out); + final ZipInputStream zin = new ZipInputStream(in); + try { + ZipEntry entry; + String name, dir; + while ((entry = zin.getNextEntry()) != null) { + name = entry.getName(); + if (entry.isDirectory()) { + mkdirs(outdir, name); + continue; + } + /* + * this part is necessary because file entry can come before directory entry where is file located i.e.: /foo/foo.txt /foo/ + */ + dir = getDirectoryPart(name); + if (dir != null) + mkdirs(outdir, dir); + + extractFile(zin, outdir, name, iListener); + } + } finally { + zin.close(); + } + } + + private static void extractFile(final ZipInputStream in, final File outdir, final String name, + final OCommandOutputListener iListener) throws IOException { + if (iListener != null) + iListener.onMessage("\n- Uncompressing file " + name + "..."); + + final BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(outdir, name))); + try { + OIOUtils.copyStream(in, out, -1); + } finally { + out.close(); + } + } + + private static void mkdirs(final File outdir, final String path) { + final File d = new File(outdir, path); + if (!d.exists()) + d.mkdirs(); + } + + private static String getDirectoryPart(final String name) { + final int s = name.lastIndexOf(File.separatorChar); + return s == -1 ? null : name.substring(0, s); + } + + private static void addFolder(ZipOutputStream zos, String path, String baseFolderName, final String[] iSkipFileExtensions, + final OCommandOutputListener iOutput, final List iCompressedFiles) throws IOException { + + File f = new File(path); + if (f.exists()) { + if (f.isDirectory()) { + File f2[] = f.listFiles(); + for (int i = 0; i < f2.length; i++) { + addFolder(zos, f2[i].getAbsolutePath(), baseFolderName, iSkipFileExtensions, iOutput, iCompressedFiles); + } + } else { + // add file + // extract the relative name for entry purpose + String entryName = path.substring(baseFolderName.length() + 1, path.length()); + + if (iSkipFileExtensions != null) + for (String skip : iSkipFileExtensions) + if (entryName.endsWith(skip)) + return; + + iCompressedFiles.add(path); + + addFile(zos, path, entryName, iOutput); + } + + } else { + throw new IllegalArgumentException("Directory " + path + " not found"); + } + } + + public static void compressFile(final String folderName, final String entryName, final OutputStream output, + final OCommandOutputListener iOutput, final int compressionLevel) throws IOException { + final ZipOutputStream zos = new ZipOutputStream(output); + zos.setComment("OrientDB Backup executed on " + new Date()); + try { + zos.setLevel(compressionLevel); + addFile(zos, folderName + "/" + entryName, entryName, iOutput); + } finally { + zos.close(); + } + } + + public static void compressFiles(final String folderName, final String[] entryNames, final OutputStream output, + final OCommandOutputListener iOutput, final int compressionLevel) throws IOException { + final ZipOutputStream zos = new ZipOutputStream(output); + zos.setComment("OrientDB Backup executed on " + new Date()); + try { + zos.setLevel(compressionLevel); + for (String entryName : entryNames) + addFile(zos, folderName + "/" + entryName, entryName, iOutput); + } finally { + zos.close(); + } + } + + private static void addFile(final ZipOutputStream zos, final String folderName, final String entryName, + final OCommandOutputListener iOutput) throws IOException { + final long begin = System.currentTimeMillis(); + + if (iOutput != null) + iOutput.onMessage("\n- Compressing file " + entryName + "..."); + + final ZipEntry ze = new ZipEntry(entryName); + zos.putNextEntry(ze); + try { + final FileInputStream in = new FileInputStream(folderName); + try { + OIOUtils.copyStream(in, zos, -1); + } finally { + in.close(); + } + } catch (IOException e) { + if (iOutput != null) + iOutput.onMessage("error: " + e); + + OLogManager.instance().error(OZIPCompression.class, "Cannot compress file: %s", e, folderName); + throw e; + } finally { + zos.closeEntry(); + } + + if (iOutput != null) { + final long ratio = ze.getSize() > 0 ? 100 - (ze.getCompressedSize() * 100 / ze.getSize()) : 0; + + iOutput.onMessage("ok size=" + OFileUtils.getSizeAsString(ze.getSize()) + " compressedSize=" + ze.getCompressedSize() + + " ratio=" + ratio + "%% elapsed=" + OIOUtils.getTimeAsString(System.currentTimeMillis() - begin) + ""); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OConfigurationChangeCallback.java b/core/src/main/java/com/orientechnologies/orient/core/config/OConfigurationChangeCallback.java new file mode 100644 index 00000000000..356002b089d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OConfigurationChangeCallback.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +/** + * Interface of actions to call in case of change of the configuration. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OConfigurationChangeCallback { + public void change(final Object iCurrentValue, final Object iNewValue); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OContextConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OContextConfiguration.java new file mode 100644 index 00000000000..ccc8c860bb5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OContextConfiguration.java @@ -0,0 +1,135 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Represents a context configuration where custom setting could be defined for the context only. If not defined, globals will be + * taken. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OContextConfiguration implements Serializable { + private final Map config = new ConcurrentHashMap(); + + /** + * Empty constructor to create just a proxy for the OGlobalConfiguration. No values are setted. + */ + public OContextConfiguration() { + } + + /** + * Initializes the context with custom parameters. + * + * @param iConfig + * Map of parameters of type Map. + */ + public OContextConfiguration(final Map iConfig) { + this.config.putAll(iConfig); + } + + public OContextConfiguration(final OContextConfiguration iParent) { + if (iParent != null) + config.putAll(iParent.config); + } + + public Object setValue(final OGlobalConfiguration iConfig, final Object iValue) { + if (iValue == null) + return config.remove(iConfig.getKey()); + + return config.put(iConfig.getKey(), iValue); + } + + public Object setValue(final String iName, final Object iValue) { + if (iValue == null) + return config.remove(iName); + + return config.put(iName, iValue); + } + + public Object getValue(final OGlobalConfiguration iConfig) { + if (config != null && config.containsKey(iConfig.getKey())) + return config.get(iConfig.getKey()); + return iConfig.getValue(); + } + + @SuppressWarnings("unchecked") + public T getValue(final String iName, final T iDefaultValue) { + if (config != null && config.containsKey(iName)) + return (T) config.get(iName); + + final String sysProperty = System.getProperty(iName); + if (sysProperty != null) + return (T) sysProperty; + + return iDefaultValue; + } + + public boolean getValueAsBoolean(final OGlobalConfiguration iConfig) { + final Object v = getValue(iConfig); + if( v == null ) + return false; + return v instanceof Boolean ? ((Boolean) v).booleanValue() : Boolean.parseBoolean(v.toString()); + } + + public String getValueAsString(final String iName, final String iDefaultValue) { + return getValue(iName, iDefaultValue); + } + + public String getValueAsString(final OGlobalConfiguration iConfig) { + final Object v = getValue(iConfig); + if (v == null) + return null; + return v.toString(); + } + + public int getValueAsInteger(final OGlobalConfiguration iConfig) { + final Object v = getValue(iConfig); + if (v == null) + return 0; + return v instanceof Integer ? ((Integer) v).intValue() : Integer.parseInt(v.toString()); + } + + public long getValueAsLong(final OGlobalConfiguration iConfig) { + final Object v = getValue(iConfig); + if (v == null) + return 0; + return v instanceof Long ? ((Long) v).intValue() : Long.parseLong(v.toString()); + } + + public float getValueAsFloat(final OGlobalConfiguration iConfig) { + final Object v = getValue(iConfig); + if (v == null) + return 0; + return v instanceof Float ? ((Float) v).floatValue() : Float.parseFloat(v.toString()); + } + + public int getContextSize() { + return config.size(); + } + + public java.util.Set getContextKeys() { + return config.keySet(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OGlobalConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OGlobalConfiguration.java new file mode 100755 index 00000000000..e23d25a88a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OGlobalConfiguration.java @@ -0,0 +1,1165 @@ +/* +* +* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) +* * +* * Licensed 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. +* * +* * For more information: http://www.orientechnologies.com +* +*/ +package com.orientechnologies.orient.core.config; + +import com.orientechnologies.common.io.OFileUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.common.util.OApi; +import com.orientechnologies.orient.core.OConstants; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.cache.ORecordCacheWeakRefs; +import com.orientechnologies.orient.core.engine.local.OEngineLocalPaginated; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.storage.OChecksumMode; + +import java.io.PrintStream; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Level; + +/** + * Keeps all configuration settings. At startup assigns the configuration values by reading system properties. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public enum OGlobalConfiguration { + // ENVIRONMENT + ENVIRONMENT_DUMP_CFG_AT_STARTUP("environment.dumpCfgAtStartup", "Dumps the configuration during application startup", + Boolean.class, Boolean.FALSE), + + ENVIRONMENT_CONCURRENT("environment.concurrent", + "Specifies if running in multi-thread environment. Setting this to false turns off the internal lock management", + Boolean.class, Boolean.TRUE), + + ENVIRONMENT_LOCK_MANAGER_CONCURRENCY_LEVEL("environment.lockManager.concurrency.level", "Concurrency level of lock manager", + Integer.class, Runtime.getRuntime().availableProcessors() << 3, false), + + ENVIRONMENT_ALLOW_JVM_SHUTDOWN("environment.allowJVMShutdown", "Allows the shutdown of the JVM, if needed/requested", + Boolean.class, true, true), + + // SCRIPT + SCRIPT_POOL("script.pool.maxSize", "Maximum number of instances in the pool of script engines", Integer.class, 20), + + // MEMORY + MEMORY_USE_UNSAFE("memory.useUnsafe", "Indicates whether Unsafe will be used, if it is present", Boolean.class, true), + + MEMORY_CHUNK_SIZE("memory.chunk.size", "Size of single memory chunk (in bytes) which will be preallocated by OrientDB", + Integer.class, Integer.MAX_VALUE), + + DIRECT_MEMORY_SAFE_MODE("memory.directMemory.safeMode", + "Indicates whether to perform a range check before each direct memory update. It is true by default, " + + "but usually it can be safely set to false. It should only be to true after dramatic changes have been made in the storage structures", + Boolean.class, true), + + DIRECT_MEMORY_TRACK_MODE("memory.directMemory.trackMode", + "Activates the direct memory pool [leak detector](Leak-Detector.md). This detector causes a large overhead and should be used for debugging " + + "purposes only. It's also a good idea to pass the " + + "-Djava.util.logging.manager=com.orientechnologies.common.log.OLogManager$DebugLogManager switch to the JVM, " + + "if you use this mode, this will enable the logging from JVM shutdown hooks.", Boolean.class, false), + + DIRECT_MEMORY_ONLY_ALIGNED_ACCESS("memory.directMemory.onlyAlignedMemoryAccess", + "Some architectures do not allow unaligned memory access or may suffer from speed degradation. For such platforms, this flag should be set to true", + Boolean.class, true), + + JVM_GC_DELAY_FOR_OPTIMIZE("jvm.gc.delayForOptimize", + "Minimal amount of time (in seconds), since the last System.gc(), when called after tree optimization", Long.class, 600), + + // STORAGE + /** + * Limit of amount of files which may be open simultaneously + */ + OPEN_FILES_LIMIT("storage.openFiles.limit", "Limit of amount of files which may be open simultaneously", Integer.class, 512), + + /** + * Amount of cached locks is used for component lock in atomic operation to avoid constant creation of new lock instances, default + * value is 10000. + */ + COMPONENTS_LOCK_CACHE("storage.componentsLock.cache", + "Amount of cached locks is used for component lock to avoid constant creation of new lock instances", Integer.class, 10000), + + DISK_CACHE_PINNED_PAGES("storage.diskCache.pinnedPages", "Maximum amount of pinned pages which may be contained in cache," + + " if this percent is reached next pages will be left in unpinned state. You can not set value more than 50", Integer.class, + 20, false), + + DISK_CACHE_SIZE("storage.diskCache.bufferSize", "Size of disk buffer in megabytes, disk size may be changed at runtime, " + + "but if does not enough to contain all pinned pages exception will be thrown", Integer.class, 4 * 1024, + new OConfigurationChangeCallback() { + + @Override + public void change(Object currentValue, Object newValue) { + final OEngineLocalPaginated engineLocalPaginated = (OEngineLocalPaginated) Orient.instance() + .getEngineIfRunning(OEngineLocalPaginated.NAME); + if (engineLocalPaginated != null) + engineLocalPaginated.changeCacheSize(((Integer) (newValue)) * 1024L * 1024L); + } + }), + + DISK_WRITE_CACHE_PART("storage.diskCache.writeCachePart", "Percentage of disk cache, which is used as write cache", Integer.class, + 15), + + DISK_WRITE_CACHE_PAGE_TTL("storage.diskCache.writeCachePageTTL", + "Max time until a page will be flushed from write cache (in seconds)", Long.class, 24 * 60 * 60), + + DISK_WRITE_CACHE_PAGE_FLUSH_INTERVAL("storage.diskCache.writeCachePageFlushInterval", + "Interval between flushing of pages from write cache (in ms)", Integer.class, 25), + + DISK_WRITE_CACHE_FLUSH_WRITE_INACTIVITY_INTERVAL("storage.diskCache.writeCacheFlushInactivityInterval", + "Interval between 2 writes to the disk cache," + + " if writes are done with an interval more than provided, all files will be fsynced before the next write," + + " which allows a data restore after a server crash (in ms)", Long.class, 60 * 1000), + + DISK_WRITE_CACHE_FLUSH_LOCK_TIMEOUT("storage.diskCache.writeCacheFlushLockTimeout", + "Maximum amount of time the write cache will wait before a page flushes (in ms, -1 to disable)", Integer.class, -1), + + @Deprecated DISC_CACHE_FREE_SPACE_CHECK_INTERVAL("storage.diskCache.diskFreeSpaceCheckInterval", + "The interval (in seconds), after which the storage periodically " + + "checks whether the amount of free disk space is enough to work in write mode", Integer.class, 5), + + /** + * The interval (how many new pages should be added before free space will be checked), after which the storage periodically + * checks whether the amount of free disk space is enough to work in write mode. + */ + DISC_CACHE_FREE_SPACE_CHECK_INTERVAL_IN_PAGES("storage.diskCache.diskFreeSpaceCheckIntervalInPages", + "The interval (how many new pages should be added before free space will be checked), after which the storage periodically " + + "checks whether the amount of free disk space is enough to work in write mode", Integer.class, 2048), + + /** + * Keep disk cache state between moment when storage is closed and moment when it is opened again. true by default. + */ + STORAGE_KEEP_DISK_CACHE_STATE("storage.diskCache.keepState", + "Keep disk cache state between moment when storage is closed and moment when it is opened again. true by default", + Boolean.class, true), + + STORAGE_CHECKSUM_MODE("storage.diskCache.checksumMode", "Controls the per-page checksum storage and verification done by " + + "the file cache. Possible modes: 'off' – checksums are completely off; 'store' – checksums are calculated and stored " + + "on page flushes, no verification is done on page loads, stored checksums are verified only during user-initiated health " + + "checks; 'storeAndVerify' (default) – checksums are calculated and stored on page flushes, verification is performed on " + + "each page load, errors are reported in the log; 'storeAndThrow' – same as `storeAndVerify` with addition of exceptions " + + "thrown on errors, this mode is useful for debugging and testing, but should be avoided in a production environment.", + OChecksumMode.class, OChecksumMode.StoreAndVerify, false), + + STORAGE_CONFIGURATION_SYNC_ON_UPDATE("storage.configuration.syncOnUpdate", + "Indicates a force sync should be performed for each update on the storage configuration", Boolean.class, true), + + STORAGE_COMPRESSION_METHOD("storage.compressionMethod", "Record compression method used in storage" + + " Possible values : gzip, nothing, snappy, snappy-native. Default is 'nothing' that means no compression", String.class, + "nothing"), + + STORAGE_ENCRYPTION_METHOD("storage.encryptionMethod", + "Record encryption method used in storage" + " Possible values : 'aes' and 'des'. Default is 'nothing' for no encryption", + String.class, "nothing"), + + STORAGE_ENCRYPTION_KEY("storage.encryptionKey", "Contains the storage encryption key. This setting is hidden", String.class, null, + false, true), + + STORAGE_MAKE_FULL_CHECKPOINT_AFTER_CREATE("storage.makeFullCheckpointAfterCreate", + "Indicates whether a full checkpoint should be performed, if storage was created", Boolean.class, false), + + STORAGE_MAKE_FULL_CHECKPOINT_AFTER_OPEN("storage.makeFullCheckpointAfterOpen", + "Indicates whether a full checkpoint should be performed, if storage was opened. It is needed so fuzzy checkpoints can work properly", + Boolean.class, true), + + STORAGE_MAKE_FULL_CHECKPOINT_AFTER_CLUSTER_CREATE("storage.makeFullCheckpointAfterClusterCreate", + "Indicates whether a full checkpoint should be performed, if storage was opened", Boolean.class, true), + + STORAGE_TRACK_CHANGED_RECORDS_IN_WAL("storage.trackChangedRecordsInWAL", + "If this flag is set metadata which contains rids of changed records is added at the end of each atomic operation", + Boolean.class, false), + + USE_WAL("storage.useWAL", "Whether WAL should be used in paginated storage", Boolean.class, true), + + WAL_SYNC_ON_PAGE_FLUSH("storage.wal.syncOnPageFlush", "Indicates whether a force sync should be performed during WAL page flush", + Boolean.class, true), + + WAL_CACHE_SIZE("storage.wal.cacheSize", + "Maximum size of WAL cache (in amount of WAL pages, each page is 64k) If set to 0, caching will be disabled", Integer.class, + 3000), + + WAL_FILE_AUTOCLOSE_INTERVAL("storage.wal.fileAutoCloseInterval", + "Interval in seconds after which WAL file will be closed if there is no " + + "any IO operations on this file (in seconds), default value is 10", Integer.class, 10, false), + + WAL_MAX_SEGMENT_SIZE("storage.wal.maxSegmentSize", "Maximum size of single WAL segment (in megabytes)", Integer.class, 128), + + WAL_MAX_SIZE("storage.wal.maxSize", "Maximum size of WAL on disk (in megabytes)", Integer.class, 4096), + + WAL_COMMIT_TIMEOUT("storage.wal.commitTimeout", "Maximum interval between WAL commits (in ms.)", Integer.class, 1000), + + WAL_SHUTDOWN_TIMEOUT("storage.wal.shutdownTimeout", "Maximum wait interval between events, when the background flush thread" + + "receives a shutdown command and when the background flush will be stopped (in ms.)", Integer.class, 10000), + + WAL_FUZZY_CHECKPOINT_INTERVAL("storage.wal.fuzzyCheckpointInterval", "Interval between fuzzy checkpoints (in seconds)", + Integer.class, 300), + + WAL_REPORT_AFTER_OPERATIONS_DURING_RESTORE("storage.wal.reportAfterOperationsDuringRestore", + "Amount of processed log operations, after which status of data restore procedure will be printed (0 or a negative value, disables the logging)", + Integer.class, 10000), + + WAL_RESTORE_BATCH_SIZE("storage.wal.restore.batchSize", + "Amount of WAL records, which are read at once in a single batch during a restore procedure", Integer.class, 1000), + + @Deprecated WAL_READ_CACHE_SIZE("storage.wal.readCacheSize", "Size of WAL read cache in amount of pages", Integer.class, 1000), + + WAL_FUZZY_CHECKPOINT_SHUTDOWN_TIMEOUT("storage.wal.fuzzyCheckpointShutdownWait", + "The amount of time the DB should wait until it shuts down (in seconds)", Integer.class, 60 * 10), + + WAL_FULL_CHECKPOINT_SHUTDOWN_TIMEOUT("storage.wal.fullCheckpointShutdownTimeout", + "The amount of time the DB will wait, until a checkpoint is finished, during a DB shutdown (in seconds)", Integer.class, + 60 * 10), + + WAL_LOCATION("storage.wal.path", "Path to the WAL file on the disk. By default, it is placed in the DB directory, but" + + " it is highly recommended to use a separate disk to store log operations", String.class, null), + + DISK_CACHE_PAGE_SIZE("storage.diskCache.pageSize", "Size of page of disk buffer (in kilobytes). !!! NEVER CHANGE THIS VALUE !!!", + Integer.class, 64), + + DISK_CACHE_FREE_SPACE_LIMIT("storage.diskCache.diskFreeSpaceLimit", "Minimum amount of space on disk, which, when exceeded, " + + "will cause the database to switch to read-only mode (in megabytes)", Long.class, + 2 * WAL_MAX_SEGMENT_SIZE.getValueAsLong()), + + PAGINATED_STORAGE_LOWEST_FREELIST_BOUNDARY("storage.lowestFreeListBound", + "The least amount of free space (in kb) in a page, which is tracked in paginated storage", Integer.class, 16), + + STORAGE_LOCK_TIMEOUT("storage.lockTimeout", "Maximum amount of time (in ms) to lock the storage", Integer.class, 0), + + STORAGE_RECORD_LOCK_TIMEOUT("storage.record.lockTimeout", "Maximum of time (in ms) to lock a shared record", Integer.class, 2000), + + STORAGE_USE_TOMBSTONES("storage.useTombstones", + "When a record is deleted, the space in the cluster will not be freed, but rather tombstoned", Boolean.class, false), + + // RECORDS + RECORD_DOWNSIZING_ENABLED("record.downsizing.enabled", + "On updates, if the record size is lower than before, this reduces the space taken accordingly. " + + "If enabled this could increase defragmentation, but it reduces the used disk space", Boolean.class, true), + + // DATABASE + OBJECT_SAVE_ONLY_DIRTY("object.saveOnlyDirty", "Object Database only! It saves objects bound to dirty records", Boolean.class, + false, true), + + // DATABASE + DB_POOL_MIN("db.pool.min", "Default database pool minimum size", Integer.class, 1), + + DB_POOL_MAX("db.pool.max", "Default database pool maximum size", Integer.class, 100), + + DB_POOL_IDLE_TIMEOUT("db.pool.idleTimeout", "Timeout for checking for free databases in the pool", Integer.class, 0), + + DB_POOL_IDLE_CHECK_DELAY("db.pool.idleCheckDelay", "Delay time on checking for idle databases", Integer.class, 0), + + DB_MVCC_THROWFAST("db.mvcc.throwfast", + "Use fast-thrown exceptions for MVCC OConcurrentModificationExceptions. No context information will be available. " + + "Set to true, when these exceptions are thrown, but the details are not necessary", Boolean.class, false, true), + + DB_VALIDATION("db.validation", "Enables or disables validation of records", Boolean.class, true, true), + + // SETTINGS OF NON-TRANSACTIONAL MODE + NON_TX_RECORD_UPDATE_SYNCH("nonTX.recordUpdate.synch", + "Executes a sync against the file-system for every record operation. This slows down record updates, " + + "but guarantees reliability on unreliable drives", Boolean.class, Boolean.FALSE), + + NON_TX_CLUSTERS_SYNC_IMMEDIATELY("nonTX.clusters.sync.immediately", + "List of clusters to sync immediately after update (separated by commas). Can be useful for a manual index", String.class, + OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME), + + // TRANSACTIONS + + TX_TRACK_ATOMIC_OPERATIONS("tx.trackAtomicOperations", + "This setting is used only for debug purposes. It creates a stack trace of methods, when an atomic operation is started", + Boolean.class, false), + + TX_PAGE_CACHE_SIZE("tx.pageCacheSize", + "The size of a per-transaction page cache in pages, 12 by default, 0 to disable the cache.", Integer.class, 12), + + // INDEX + INDEX_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD("index.embeddedToSbtreeBonsaiThreshold", + "Amount of values, after which the index implementation will use an sbtree as a values container. Set to -1, to disable and force using an sbtree", + Integer.class, 40, true), + + INDEX_SBTREEBONSAI_TO_EMBEDDED_THRESHOLD("index.sbtreeBonsaiToEmbeddedThreshold", + "Amount of values, after which index implementation will use an embedded values container (disabled by default)", + Integer.class, -1, true), + + HASH_TABLE_SPLIT_BUCKETS_BUFFER_LENGTH("hashTable.slitBucketsBuffer.length", "Length of buffer (in pages), where buckets " + + "that were split, but not flushed to the disk, are kept. This buffer is used to minimize random IO overhead", Integer.class, + 1500), + + INDEX_SYNCHRONOUS_AUTO_REBUILD("index.auto.synchronousAutoRebuild", + "Synchronous execution of auto rebuilding of indexes, in case of a DB crash", Boolean.class, Boolean.TRUE), + + INDEX_AUTO_LAZY_UPDATES("index.auto.lazyUpdates", + "Configure the TreeMaps for automatic indexes, as buffered or not. -1 means buffered until tx.commit() or db.close() are called", + Integer.class, 10000), + + INDEX_FLUSH_AFTER_CREATE("index.flushAfterCreate", "Flush storage buffer after index creation", Boolean.class, true), + + INDEX_MANUAL_LAZY_UPDATES("index.manual.lazyUpdates", + "Configure the TreeMaps for manual indexes as buffered or not. -1 means buffered until tx.commit() or db.close() are called", + Integer.class, 1), + + INDEX_DURABLE_IN_NON_TX_MODE("index.durableInNonTxMode", + "Indicates whether index implementation for plocal storage will be durable in non-Tx mode (true by default)", Boolean.class, + true), + + /** + * @see OIndexDefinition#isNullValuesIgnored() + * @since 2.2 + */ + INDEX_IGNORE_NULL_VALUES_DEFAULT("index.ignoreNullValuesDefault", + "Controls whether null values will be ignored by default " + "by newly created indexes or not (false by default)", + Boolean.class, false), + + INDEX_TX_MODE("index.txMode", + "Indicates the index durability level in TX mode. Can be ROLLBACK_ONLY or FULL (ROLLBACK_ONLY by default)", String.class, + "FULL"), + + INDEX_CURSOR_PREFETCH_SIZE("index.cursor.prefetchSize", "Default prefetch size of index cursor", Integer.class, 500000), + + // SBTREE + SBTREE_MAX_DEPTH("sbtree.maxDepth", + "Maximum depth of sbtree, which will be traversed during key look up until it will be treated as broken (64 by default)", + Integer.class, 64), + + SBTREE_MAX_KEY_SIZE("sbtree.maxKeySize", "Maximum size of a key, which can be put in the SBTree in bytes (10240 by default)", + Integer.class, 10240), + + SBTREE_MAX_EMBEDDED_VALUE_SIZE("sbtree.maxEmbeddedValueSize", + "Maximum size of value which can be put in an SBTree without creation link to a standalone page in bytes (40960 by default)", + Integer.class, 40960), + + SBTREEBONSAI_BUCKET_SIZE("sbtreebonsai.bucketSize", + "Size of bucket in OSBTreeBonsai (in kB). Contract: bucketSize < storagePageSize, storagePageSize % bucketSize == 0", + Integer.class, 2), + + SBTREEBONSAI_LINKBAG_CACHE_SIZE("sbtreebonsai.linkBagCache.size", + "Amount of LINKBAG collections to be cached, to avoid constant reloading of data", Integer.class, 100000), + + SBTREEBONSAI_LINKBAG_CACHE_EVICTION_SIZE("sbtreebonsai.linkBagCache.evictionSize", + "The number of cached LINKBAG collections, which will be removed, when the cache limit is reached", Integer.class, 1000), + + SBTREEBOSAI_FREE_SPACE_REUSE_TRIGGER("sbtreebonsai.freeSpaceReuseTrigger", + "How much free space should be in an sbtreebonsai file, before it will be reused during the next allocation", Float.class, + 0.5), + + // RIDBAG + RID_BAG_EMBEDDED_DEFAULT_SIZE("ridBag.embeddedDefaultSize", "Size of embedded RidBag array, when created (empty)", Integer.class, + 4), + + RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD("ridBag.embeddedToSbtreeBonsaiThreshold", + "Amount of values after which a LINKBAG implementation will use sbtree as values container. Set to -1 to always use an sbtree", + Integer.class, 40, true), + + RID_BAG_SBTREEBONSAI_TO_EMBEDDED_THRESHOLD("ridBag.sbtreeBonsaiToEmbeddedToThreshold", + "Amount of values, after which a LINKBAG implementation will use an embedded values container (disabled by default)", + Integer.class, -1, true), + + // COLLECTIONS + PREFER_SBTREE_SET("collections.preferSBTreeSet", "This configuration setting is experimental", Boolean.class, false), + + // FILE + @Deprecated TRACK_FILE_CLOSE("file.trackFileClose", + "Log all the cases when files are closed. This is needed only for internal debugging purposes", Boolean.class, false), + + FILE_LOCK("file.lock", "Locks files when used. Default is true", boolean.class, true), + + FILE_DELETE_DELAY("file.deleteDelay", "Delay time (in ms) to wait for another attempt to delete a locked file", Integer.class, + 10), + + FILE_DELETE_RETRY("file.deleteRetry", "Number of retries to delete a locked file", Integer.class, 50), + + // SECURITY + SECURITY_USER_PASSWORD_SALT_ITERATIONS("security.userPasswordSaltIterations", + "Number of iterations to generate the salt or user password. Changing this setting does not affect stored passwords", + Integer.class, 65536), + + SECURITY_USER_PASSWORD_SALT_CACHE_SIZE("security.userPasswordSaltCacheSize", + "Cache size of hashed salt passwords. The cache works as LRU. Use 0 to disable the cache", Integer.class, 500), + + SECURITY_USER_PASSWORD_DEFAULT_ALGORITHM("security.userPasswordDefaultAlgorithm", + "Default encryption algorithm used for passwords hashing", String.class, "PBKDF2WithHmacSHA256"), + + // NETWORK + NETWORK_MAX_CONCURRENT_SESSIONS("network.maxConcurrentSessions", "Maximum number of concurrent sessions", Integer.class, 1000, + true), + + NETWORK_SOCKET_BUFFER_SIZE("network.socketBufferSize", "TCP/IP Socket buffer size, if 0 use the OS default", Integer.class, 0, + true), + + NETWORK_LOCK_TIMEOUT("network.lockTimeout", "Timeout (in ms) to acquire a lock against a channel", Integer.class, 15000, true), + + NETWORK_SOCKET_TIMEOUT("network.socketTimeout", "TCP/IP Socket timeout (in ms)", Integer.class, 15000, true), + + NETWORK_REQUEST_TIMEOUT("network.requestTimeout", "Request completion timeout (in ms)", Integer.class, 3600000 /* one hour */, + true), + + NETWORK_SOCKET_RETRY("network.retry", "Number of attempts to connect to the server on failure", Integer.class, 5, true), + + NETWORK_SOCKET_RETRY_DELAY("network.retryDelay", + "The time (in ms) the client must wait, before reconnecting to the server on failure", Integer.class, 500, true), + + NETWORK_BINARY_DNS_LOADBALANCING_ENABLED("network.binary.loadBalancing.enabled", + "Asks for DNS TXT record, to determine if load balancing is supported", Boolean.class, Boolean.FALSE, true), + + NETWORK_BINARY_DNS_LOADBALANCING_TIMEOUT("network.binary.loadBalancing.timeout", + "Maximum time (in ms) to wait for the answer from DNS about the TXT record for load balancing", Integer.class, 2000, true), + + NETWORK_BINARY_MAX_CONTENT_LENGTH("network.binary.maxLength", "TCP/IP max content length (in KB) of BINARY requests", + Integer.class, 16384, true), + + NETWORK_BINARY_READ_RESPONSE_MAX_TIMES("network.binary.readResponse.maxTimes", + "Maximum attempts, until a response can be read. Otherwise, the response will be dropped from the channel", Integer.class, 20, + true), + + NETWORK_BINARY_DEBUG("network.binary.debug", "Debug mode: print all data incoming on the binary channel", Boolean.class, false, + true), + + // HTTP + + /** + * Since v2.2.8 + */ + NETWORK_HTTP_INSTALL_DEFAULT_COMMANDS("network.http.installDefaultCommands", "Installs the default HTTP commands", Boolean.class, + Boolean.TRUE, true), + + NETWORK_HTTP_SERVER_INFO("network.http.serverInfo", + "Server info to send in HTTP responses. Change the default if you want to hide it is a OrientDB Server", String.class, + "OrientDB Server v." + OConstants.getVersion(), true), + + NETWORK_HTTP_MAX_CONTENT_LENGTH("network.http.maxLength", "TCP/IP max content length (in bytes) for HTTP requests", Integer.class, + 1000000, true), + + NETWORK_HTTP_STREAMING("network.http.streaming", "Enable Http chunked streaming for json responses", Boolean.class, false, true), + + NETWORK_HTTP_CONTENT_CHARSET("network.http.charset", "Http response charset", String.class, "utf-8", true), + + NETWORK_HTTP_JSON_RESPONSE_ERROR("network.http.jsonResponseError", "Http response error in json", Boolean.class, true, true), + + NETWORK_HTTP_JSONP_ENABLED("network.http.jsonp", + "Enable the usage of JSONP, if requested by the client. The parameter name to use is 'callback'", Boolean.class, false, true), + + NETWORK_HTTP_SESSION_EXPIRE_TIMEOUT("network.http.sessionExpireTimeout", + "Timeout, after which an http session is considered to have expired (in seconds)", Integer.class, 300), + + NETWORK_HTTP_USE_TOKEN("network.http.useToken", "Enable Token based sessions for http", Boolean.class, false), + + NETWORK_TOKEN_SECRETKEY("network.token.secretKey", "Network token sercret key", String.class, ""), + + NETWORK_TOKEN_ENCRYPTION_ALGORITHM("network.token.encryptionAlgorithm", "Network token algorithm", String.class, "HmacSHA256"), + + NETWORK_TOKEN_EXPIRE_TIMEOUT("network.token.expireTimeout", + "Timeout, after which a binary session is considered to have expired (in minutes)", Integer.class, 60), + + // PROFILER + + PROFILER_ENABLED("profiler.enabled", "Enables the recording of statistics and counters", Boolean.class, false, + new OConfigurationChangeCallback() { + public void change(final Object iCurrentValue, final Object iNewValue) { + final OProfiler prof = Orient.instance().getProfiler(); + if (prof != null) + if ((Boolean) iNewValue) + prof.startRecording(); + else + prof.stopRecording(); + } + }), + + PROFILER_CONFIG("profiler.config", "Configures the profiler as ,,", + String.class, null, new OConfigurationChangeCallback() { + public void change(final Object iCurrentValue, final Object iNewValue) { + Orient.instance().getProfiler().configure(iNewValue.toString()); + } + }), + + PROFILER_AUTODUMP_INTERVAL("profiler.autoDump.interval", "Dumps the profiler values at regular intervals (in seconds)", + Integer.class, 0, new OConfigurationChangeCallback() { + public void change(final Object iCurrentValue, final Object iNewValue) { + Orient.instance().getProfiler().setAutoDump((Integer) iNewValue); + } + }), + + PROFILER_MAXVALUES("profiler.maxValues", "Maximum values to store. Values are managed in a LRU", Integer.class, 200), + + PROFILER_MEMORYCHECK_INTERVAL("profiler.memoryCheckInterval", + "Checks the memory usage every configured milliseconds. Use 0 to disable it", Long.class, 120000), + + // SEQUENCES + + SEQUENCE_MAX_RETRY("sequence.maxRetry", "Maximum number of retries between attempt to change a sequence in concurrent mode", + Integer.class, 100), + + SEQUENCE_RETRY_DELAY("sequence.retryDelay", + "Maximum number of ms to wait between concurrent modification exceptions. The value is computed as random between 1 and this number", + Integer.class, 200), + + /** + * Interval between snapshots of profiler state in milliseconds, default value is 100. + */ + STORAGE_PROFILER_SNAPSHOT_INTERVAL("storageProfiler.intervalBetweenSnapshots", + "Interval between snapshots of profiler state in milliseconds", Integer.class, 100), + + STORAGE_PROFILER_CLEANUP_INTERVAL("storageProfiler.cleanUpInterval", "Interval between time series in milliseconds", + Integer.class, 5000), + + // LOG + LOG_CONSOLE_LEVEL("log.console.level", "Console logging level", String.class, "info", new OConfigurationChangeCallback() { + public void change(final Object iCurrentValue, final Object iNewValue) { + OLogManager.instance().setLevel((String) iNewValue, ConsoleHandler.class); + } + }), + + LOG_FILE_LEVEL("log.file.level", "File logging level", String.class, "info", new OConfigurationChangeCallback() { + public void change(final Object iCurrentValue, final Object iNewValue) { + OLogManager.instance().setLevel((String) iNewValue, FileHandler.class); + } + }), + + // CLASS + CLASS_MINIMUM_CLUSTERS("class.minimumClusters", "Minimum clusters to create when a new class is created. 0 means Automatic", + Integer.class, 0), + + // LOG + LOG_SUPPORTS_ANSI("log.console.ansi", + "ANSI Console support. 'auto' means automatic check if it is supported, 'true' to force using ANSI, 'false' to avoid using ANSI", + String.class, "auto"), + + // CACHE + CACHE_LOCAL_IMPL("cache.local.impl", "Local Record cache implementation", String.class, ORecordCacheWeakRefs.class.getName()), + + // COMMAND + COMMAND_TIMEOUT("command.timeout", "Default timeout for commands (in ms)", Long.class, 0, true), + + COMMAND_CACHE_ENABLED("command.cache.enabled", "Enable command cache", Boolean.class, false), + + COMMAND_CACHE_EVICT_STRATEGY("command.cache.evictStrategy", "Command cache strategy between: [INVALIDATE_ALL,PER_CLUSTER]", + String.class, "PER_CLUSTER"), + + COMMAND_CACHE_MIN_EXECUTION_TIME("command.cache.minExecutionTime", "Minimum execution time to consider caching the result set", + Integer.class, 10), + + COMMAND_CACHE_MAX_RESULSET_SIZE("command.cache.maxResultsetSize", "Maximum resultset time to consider caching result set", + Integer.class, 500), + + // QUERY + QUERY_PARALLEL_AUTO("query.parallelAuto", "Auto enable parallel query, if requirements are met", Boolean.class, false), + + QUERY_PARALLEL_MINIMUM_RECORDS("query.parallelMinimumRecords", + "Minimum number of records to activate parallel query automatically", Long.class, 300000), + + QUERY_PARALLEL_RESULT_QUEUE_SIZE("query.parallelResultQueueSize", + "Size of the queue that holds results on parallel execution. The queue is blocking, so in case the queue is full, the query threads will be in a wait state", + Integer.class, 20000), + + QUERY_SCAN_PREFETCH_PAGES("query.scanPrefetchPages", + "Pages to prefetch during scan. Setting this value higher makes scans faster, because it reduces the number of I/O operations, though it consumes more memory. (Use 0 to disable)", + Integer.class, 20), + + QUERY_SCAN_BATCH_SIZE("query.scanBatchSize", + "Scan clusters in blocks of records. This setting reduces the lock time on the cluster during scans. A high value mean a faster execution, but also a lower concurrency level. Set to 0 to disable batch scanning. Disabling batch scanning is suggested for read-only databases only", + Long.class, 1000), + + QUERY_SCAN_THRESHOLD_TIP("query.scanThresholdTip", + "If the total number of records scanned in a query exceeds this setting, then a warning is given. (Use 0 to disable)", + Long.class, 50000), + + QUERY_LIMIT_THRESHOLD_TIP("query.limitThresholdTip", + "If the total number of returned records exceeds this value, then a warning is given. (Use 0 to disable)", Long.class, 10000), + + QUERY_LIVE_SUPPORT("query.live.support", "Enable/Disable the support of live query. (Use false to disable)", Boolean.class, true), + + QUERY_TIMEOUT_DEFAULT_STRATEGY("query.timeout.defaultStrategy", "Default timeout strategy for queries (can be RETURN or EXCEPTION)", String.class, "EXCEPTION"), + + LUCENE_QUERY_PAGE_SIZE("lucene.query.pageSize", + "Size of the page when fetching data from a lucene index", Long.class, 10000,true), + + STATEMENT_CACHE_SIZE("statement.cacheSize", "Number of parsed SQL statements kept in cache", Integer.class, 100), + + // GRAPH + SQL_GRAPH_CONSISTENCY_MODE("sql.graphConsistencyMode", + "Consistency mode for graphs. It can be 'tx' (default), 'notx_sync_repair' and 'notx_async_repair'. " + + "'tx' uses transactions to maintain consistency. Instead both 'notx_sync_repair' and 'notx_async_repair' do not use transactions, " + + "and the consistency, in case of JVM crash, is guaranteed by a database repair operation that run at startup. " + + "With 'notx_sync_repair' the repair is synchronous, so the database comes online after the repair is ended, while " + + "with 'notx_async_repair' the repair is a background process", String.class, "tx"), + + /** + * Maximum size of pool of network channels between client and server. A channel is a TCP/IP connection. + */ + CLIENT_CHANNEL_MAX_POOL("client.channel.maxPool", + "Maximum size of pool of network channels between client and server. A channel is a TCP/IP connection", Integer.class, 100), + + /** + * Maximum time, where the client should wait for a connection from the pool, when all connections busy. + */ + CLIENT_CONNECT_POOL_WAIT_TIMEOUT("client.connectionPool.waitTimeout", + "Maximum time, where the client should wait for a connection from the pool, when all connections busy", Integer.class, 5000, + true), + + CLIENT_DB_RELEASE_WAIT_TIMEOUT("client.channel.dbReleaseWaitTimeout", + "Delay (in ms), after which a data modification command will be resent, if the DB was frozen", Integer.class, 10000, true), + + CLIENT_USE_SSL("client.ssl.enabled", "Use SSL for client connections", Boolean.class, false), + + CLIENT_SSL_KEYSTORE("client.ssl.keyStore", "Use SSL for client connections", String.class, null), + + CLIENT_SSL_KEYSTORE_PASSWORD("client.ssl.keyStorePass", "Use SSL for client connections", String.class, null), + + CLIENT_SSL_TRUSTSTORE("client.ssl.trustStore", "Use SSL for client connections", String.class, null), + + CLIENT_SSL_TRUSTSTORE_PASSWORD("client.ssl.trustStorePass", "Use SSL for client connections", String.class, null), + + // SERVER + SERVER_OPEN_ALL_DATABASES_AT_STARTUP("server.openAllDatabasesAtStartup", + "If true, the server opens all the available databases at startup. Available since 2.2", Boolean.class, false), + + SERVER_CHANNEL_CLEAN_DELAY("server.channel.cleanDelay", "Time in ms of delay to check pending closed connections", Integer.class, + 5000), + + SERVER_CACHE_FILE_STATIC("server.cache.staticFile", "Cache static resources upon loading", Boolean.class, false), + + SERVER_LOG_DUMP_CLIENT_EXCEPTION_LEVEL("server.log.dumpClientExceptionLevel", + "Logs client exceptions. Use any level supported by Java java.util.logging.Level class: OFF, FINE, CONFIG, INFO, WARNING, SEVERE", + String.class, Level.FINE.getName()), + + SERVER_LOG_DUMP_CLIENT_EXCEPTION_FULLSTACKTRACE("server.log.dumpClientExceptionFullStackTrace", + "Dumps the full stack trace of the exception sent to the client", Boolean.class, Boolean.FALSE, true), + + // DISTRIBUTED + + /** + * @Since 2.2.18 + */ + DISTRIBUTED_DUMP_STATS_EVERY("distributed.dumpStatsEvery", "Time in ms to dump the cluster stats. Set to 0 to disable such dump", + Long.class, 0, true), + + DISTRIBUTED_CRUD_TASK_SYNCH_TIMEOUT("distributed.crudTaskTimeout", "Maximum timeout (in ms) to wait for CRUD remote tasks", + Long.class, 10000l, true), + + DISTRIBUTED_MAX_STARTUP_DELAY("distributed.maxStartupDelay", "Maximum delay time (in ms) to wait for a server to start", + Long.class, 10000l, true), + + DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT("distributed.commandTaskTimeout", + "Maximum timeout (in ms) to wait for command distributed tasks", Long.class, 2 * 60 * 1000l, true), + + DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT("distributed.commandQuickTaskTimeout", + "Maximum timeout (in ms) to wait for quick command distributed tasks", Long.class, 5 * 1000l, true), + + DISTRIBUTED_COMMAND_LONG_TASK_SYNCH_TIMEOUT("distributed.commandLongTaskTimeout", + "Maximum timeout (in ms) to wait for Long-running distributed tasks", Long.class, 24 * 60 * 60 * 1000, true), + + DISTRIBUTED_DEPLOYDB_TASK_SYNCH_TIMEOUT("distributed.deployDbTaskTimeout", + "Maximum timeout (in ms) to wait for database deployment", Long.class, 1200000l, true), + + DISTRIBUTED_DEPLOYCHUNK_TASK_SYNCH_TIMEOUT("distributed.deployChunkTaskTimeout", + "Maximum timeout (in ms) to wait for database chunk deployment", Long.class, 15000l, true), + + DISTRIBUTED_DEPLOYDB_TASK_COMPRESSION("distributed.deployDbTaskCompression", + "Compression level (between 0 and 9) to use in backup for database deployment", Integer.class, 7, true), + + DISTRIBUTED_ASYNCH_QUEUE_SIZE("distributed.asynchQueueSize", + "Queue size to handle distributed asynchronous operations. The bigger is the queue, the more operation are buffered, but also more memory it's consumed. 0 = dynamic allocation, which means up to 2^31-1 entries", + Integer.class, 0), + + DISTRIBUTED_ASYNCH_RESPONSES_TIMEOUT("distributed.asynchResponsesTimeout", + "Maximum timeout (in ms) to collect all the asynchronous responses from replication. After this time the operation is rolled back (through an UNDO)", + Long.class, 15000l), + + DISTRIBUTED_PURGE_RESPONSES_TIMER_DELAY("distributed.purgeResponsesTimerDelay", + "Maximum timeout (in ms) to collect all the asynchronous responses from replication. This is the delay the purge thread uses to check asynchronous requests in timeout", + Long.class, 15000l), + + /** + * @Since 2.2.7 + */ + DISTRIBUTED_CONFLICT_RESOLVER_REPAIRER_CHAIN("distributed.conflictResolverRepairerChain", + "Chain of conflict resolver implementation to use", String.class, "quorum,content,majority,version", false), + + /** + * @Since 2.2.7 + */ + DISTRIBUTED_CONFLICT_RESOLVER_REPAIRER_CHECK_EVERY("distributed.conflictResolverRepairerCheckEvery", + "Time (in ms) when the conflict resolver auto-repairer checks for records/cluster to repair", Long.class, 5000, true), + + /** + * @Since 2.2.7 + */ + DISTRIBUTED_CONFLICT_RESOLVER_REPAIRER_BATCH("distributed.conflictResolverRepairerBatch", + "Maximum number of records to repair in batch", Integer.class, 50, true), + + /** + * @Since 2.2.7 + */ + DISTRIBUTED_TX_EXPIRE_TIMEOUT("distributed.txAliveTimeout", + "Maximum timeout (in ms) a distributed transaction can be alive. This timeout is to rollback pending transactions after a while", + Long.class, 30000l, true), + + /** + * @Since 2.2.6 + */ + DISTRIBUTED_REQUEST_CHANNELS("distributed.requestChannels", "Number of network channels used to send requests", Integer.class, 1), + + /** + * @Since 2.2.6 + */ + DISTRIBUTED_RESPONSE_CHANNELS("distributed.responseChannels", "Number of network channels used to send responses", Integer.class, + 1), + + /** + * @Since 2.2.5 + */ + DISTRIBUTED_HEARTBEAT_TIMEOUT("distributed.heartbeatTimeout", + "Maximum time in ms to wait for the heartbeat. If the server does not respond in time, it is put offline", Long.class, + 10000l), + + /** + * @Since 2.2.5 + */ + DISTRIBUTED_CHECK_HEALTH_CAN_OFFLINE_SERVER("distributed.checkHealthCanOfflineServer", + "In case a server does not respond to the heartbeat message, it is set offline", Boolean.class, false), + + /** + * @Since 2.2.5 + */ + DISTRIBUTED_CHECK_HEALTH_EVERY("distributed.checkHealthEvery", "Time in ms to check the cluster health. Set to 0 to disable it", + Long.class, 10000l), + + /** + * Since 2.2.4 + */ + DISTRIBUTED_AUTO_REMOVE_OFFLINE_SERVERS("distributed.autoRemoveOfflineServers", + "This is the amount of time (in ms) the server has to be OFFLINE, before it is automatically removed from the distributed configuration. -1 = never, 0 = immediately, >0 the actual time to wait", + Long.class, 0, true), + + /** + * @Since 2.2.0 + */ + DISTRIBUTED_PUBLISH_NODE_STATUS_EVERY("distributed.publishNodeStatusEvery", + "Time in ms to publish the node status on distributed map. Set to 0 to disable such refresh of node configuration", + Long.class, 10000l, true), + + /** + * @Since 2.2.0 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_LOCAL_QUEUESIZE("distributed.localQueueSize", + "Size of the intra-thread queue for distributed messages", Integer.class, 10000), + + /** + * @Since 2.2.0 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_DB_WORKERTHREADS("distributed.dbWorkerThreads", + "Number of parallel worker threads per database that process distributed messages", Integer.class, 8), + + /** + * @Since 2.1.3, Deprecated in 2.2.0 + */ + @Deprecated @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_QUEUE_MAXSIZE("distributed.queueMaxSize", + "Maximum queue size to mark a node as stalled. If the number of messages in queue are more than this values, the node is restarted with a remote command (0 = no maximum, which means up to 2^31-1 entries)", + Integer.class, 10000), + + /** + * @Since 2.1.3 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_BACKUP_DIRECTORY("distributed.backupDirectory", + "Directory where the copy of an existent database is saved, before it is downloaded from the cluster. Leave it empty to avoid the backup.", + String.class, "../backup/databases"), + + /** + * @Since 2.2.15 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_BACKUP_TRY_INCREMENTAL_FIRST("distributed.backupTryIncrementalFirst", + "Try to execute an incremental backup first.", Boolean.class, true), + + /** + * @Since 2.1 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_CONCURRENT_TX_MAX_AUTORETRY("distributed.concurrentTxMaxAutoRetry", + "Maximum attempts the transaction coordinator should execute a transaction automatically, if records are locked. (Minimum is 1 = no attempts)", + Integer.class, 10, true), + + /** + * @Since 2.2.7 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_ATOMIC_LOCK_TIMEOUT("distributed.atomicLockTimeout", + "Timeout (in ms) to acquire a distributed lock on a record. (0=infinite)", Integer.class, 50, true), + + /** + * @Since 2.1 + */ + @OApi(maturity = OApi.MATURITY.NEW)DISTRIBUTED_CONCURRENT_TX_AUTORETRY_DELAY("distributed.concurrentTxAutoRetryDelay", + "Delay (in ms) between attempts on executing a distributed transaction, which had failed because of locked records. (0=no delay)", + Integer.class, 10, true), + + DB_DOCUMENT_SERIALIZER("db.document.serializer", "The default record serializer used by the document database", String.class, + ORecordSerializerBinary.NAME), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_KRB5_CONFIG("client.krb5.config", "Location of the Kerberos configuration file", + String.class, null), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_KRB5_CCNAME("client.krb5.ccname", "Location of the Kerberos client ticketcache", + String.class, null), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_KRB5_KTNAME("client.krb5.ktname", "Location of the Kerberos client keytab", + String.class, null), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_CREDENTIAL_INTERCEPTOR("client.credentialinterceptor", + "The name of the CredentialInterceptor class", String.class, null), + + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_CI_KEYALGORITHM("client.ci.keyalgorithm", + "The key algorithm used by the symmetric key credential interceptor", String.class, "AES"), + + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_CI_CIPHERTRANSFORM("client.ci.ciphertransform", + "The cipher transformation used by the symmetric key credential interceptor", String.class, "AES/CBC/PKCS5Padding"), + + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_CI_KEYSTORE_FILE("client.ci.keystore.file", + "The file path of the keystore used by the symmetric key credential interceptor", String.class, null), + + @OApi(maturity = OApi.MATURITY.NEW)CLIENT_CI_KEYSTORE_PASSWORD("client.ci.keystore.password", + "The password of the keystore used by the symmetric key credential interceptor", String.class, null), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)CREATE_DEFAULT_USERS("security.createDefaultUsers", + "Indicates whether default database users should be created", Boolean.class, true), + + /** + * @Since 2.2 + */ + @OApi(maturity = OApi.MATURITY.NEW)SERVER_SECURITY_FILE("server.security.file", + "Location of the OrientDB security.json configuration file", String.class, null), + + /** + * Deprecated in v2.2.0 + */ + @Deprecated JNA_DISABLE_USE_SYSTEM_LIBRARY("jna.disable.system.library", + "This property disables using JNA, should it be installed on your system. (Default true) To use JNA bundled with database", + boolean.class, true), + + @Deprecated DISTRIBUTED_QUEUE_TIMEOUT("distributed.queueTimeout", + "Maximum timeout (in ms) to wait for the response in replication", Long.class, 500000l, true), + + @Deprecated DB_MAKE_FULL_CHECKPOINT_ON_INDEX_CHANGE("db.makeFullCheckpointOnIndexChange", + "When index metadata is changed, a full checkpoint is performed", Boolean.class, true, true), + + @Deprecated DB_MAKE_FULL_CHECKPOINT_ON_SCHEMA_CHANGE("db.makeFullCheckpointOnSchemaChange", + "When index schema is changed, a full checkpoint is performed", Boolean.class, true, true), + + @Deprecated CLIENT_SESSION_TOKEN_BASED("client.session.tokenBased", "Request a token based session to the server", Boolean.class, + true), + + @Deprecated OAUTH2_SECRETKEY("oauth2.secretkey", "Http OAuth2 secret key", String.class, ""), + + @Deprecated STORAGE_USE_CRC32_FOR_EACH_RECORD("storage.cluster.usecrc32", + "Indicates whether crc32 should be used for each record to check record integrity", Boolean.class, false), + + @Deprecated LAZYSET_WORK_ON_STREAM("lazyset.workOnStream", "Deprecated, now BINARY serialization is used in place of CSV", + Boolean.class, true), + + @Deprecated DB_MVCC("db.mvcc", "Deprecated, MVCC cannot be disabled anymore", Boolean.class, true), + + @Deprecated DB_USE_DISTRIBUTED_VERSION("db.use.distributedVersion", "Deprecated, distributed version is not used anymore", + Boolean.class, Boolean.FALSE), + + @Deprecated MVRBTREE_TIMEOUT("mvrbtree.timeout", "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", + Integer.class, 0), + + @Deprecated MVRBTREE_NODE_PAGE_SIZE("mvrbtree.nodePageSize", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Integer.class, 256), + + @Deprecated MVRBTREE_LOAD_FACTOR("mvrbtree.loadFactor", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Float.class, 0.7f), + + @Deprecated MVRBTREE_OPTIMIZE_THRESHOLD("mvrbtree.optimizeThreshold", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Integer.class, 100000), + + @Deprecated MVRBTREE_ENTRYPOINTS("mvrbtree.entryPoints", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Integer.class, 64), + + @Deprecated MVRBTREE_OPTIMIZE_ENTRYPOINTS_FACTOR("mvrbtree.optimizeEntryPointsFactor", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Float.class, 1.0f), + + @Deprecated MVRBTREE_ENTRY_KEYS_IN_MEMORY("mvrbtree.entryKeysInMemory", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Boolean.class, Boolean.FALSE), + + @Deprecated MVRBTREE_ENTRY_VALUES_IN_MEMORY("mvrbtree.entryValuesInMemory", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Boolean.class, Boolean.FALSE), + + @Deprecated MVRBTREE_RID_BINARY_THRESHOLD("mvrbtree.ridBinaryThreshold", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Integer.class, -1), + + @Deprecated MVRBTREE_RID_NODE_PAGE_SIZE("mvrbtree.ridNodePageSize", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Integer.class, 64), + + @Deprecated MVRBTREE_RID_NODE_SAVE_MEMORY("mvrbtree.ridNodeSaveMemory", + "Deprecated, MVRBTREE IS NOT USED ANYMORE IN FAVOR OF SBTREE AND HASHINDEX", Boolean.class, Boolean.FALSE), + + @Deprecated TX_COMMIT_SYNCH("tx.commit.synch", "Synchronizes the storage after transaction commit", Boolean.class, false), + + @Deprecated TX_AUTO_RETRY("tx.autoRetry", + "Maximum number of automatic retry if some resource has been locked in the middle of the transaction (Timeout exception)", + Integer.class, 1), + + @Deprecated TX_LOG_TYPE("tx.log.fileType", "File type to handle transaction logs: mmap or classic", String.class, "classic"), + + @Deprecated TX_LOG_SYNCH("tx.log.synch", + "Executes a synch against the file-system at every log entry. This slows down transactions but guarantee transaction reliability on unreliable drives", + Boolean.class, Boolean.FALSE), @Deprecated TX_USE_LOG("tx.useLog", + "Transactions use log file to store temporary data to be rolled back in case of crash", Boolean.class, true), + + @Deprecated INDEX_AUTO_REBUILD_AFTER_NOTSOFTCLOSE("index.auto.rebuildAfterNotSoftClose", + "Auto rebuild all automatic indexes after upon database open when wasn't closed properly", Boolean.class, true), + + @Deprecated CLIENT_CHANNEL_MIN_POOL("client.channel.minPool", "Minimum pool size", Integer.class, 1), + + // DEPRECATED IN 2.0 + @Deprecated STORAGE_KEEP_OPEN("storage.keepOpen", "Deprecated", Boolean.class, Boolean.TRUE), + + // DEPRECATED IN 2.0, LEVEL1 CACHE CANNOT BE DISABLED ANYMORE + @Deprecated CACHE_LOCAL_ENABLED("cache.local.enabled", "Deprecated, Level1 cache cannot be disabled anymore", Boolean.class, + true), + + CLIENT_CHANNEL_IDLE_CLOSE("client.channel.idleAutoClose", "Enable the automatic close of idle sockets after a specific timeout", + Boolean.class, false), + + CLIENT_CHANNEL_IDLE_TIMEOUT("client.channel.idleTimeout", "sockets maximum time idle in seconds", Integer.class, 900); + + private final String key; + private final Object defValue; + private final Class type; + private volatile Object value = null; + private final String description; + private final OConfigurationChangeCallback changeCallback; + private final Boolean canChangeAtRuntime; + private final boolean hidden; + + static { + readConfiguration(); + } + + OGlobalConfiguration(final String iKey, final String iDescription, final Class iType, final Object iDefValue, + final OConfigurationChangeCallback iChangeAction) { + key = iKey; + description = iDescription; + defValue = iDefValue; + type = iType; + canChangeAtRuntime = true; + hidden = false; + changeCallback = iChangeAction; + } + + OGlobalConfiguration(final String iKey, final String iDescription, final Class iType, final Object iDefValue) { + this(iKey, iDescription, iType, iDefValue, false); + } + + OGlobalConfiguration(final String iKey, final String iDescription, final Class iType, final Object iDefValue, + final Boolean iCanChange) { + this(iKey, iDescription, iType, iDefValue, iCanChange, false); + } + + OGlobalConfiguration(final String iKey, final String iDescription, final Class iType, final Object iDefValue, + final boolean iCanChange, final boolean iHidden) { + key = iKey; + description = iDescription; + defValue = iDefValue; + type = iType; + canChangeAtRuntime = iCanChange; + hidden = iHidden; + changeCallback = null; + } + + public static void dumpConfiguration(final PrintStream out) { + out.print("OrientDB "); + out.print(OConstants.getVersion()); + out.println(" configuration dump:"); + + String lastSection = ""; + for (OGlobalConfiguration v : values()) { + final String section = v.key.substring(0, v.key.indexOf('.')); + + if (!lastSection.equals(section)) { + out.print("- "); + out.println(section.toUpperCase(Locale.ENGLISH)); + lastSection = section; + } + out.print(" + "); + out.print(v.key); + out.print(" = "); + out.println(v.isHidden() ? "" : String.valueOf((Object) v.getValue())); + } + } + + /** + * Find the OGlobalConfiguration instance by the key. Key is case insensitive. + * + * @param iKey Key to find. It's case insensitive. + * @return OGlobalConfiguration instance if found, otherwise null + */ + public static OGlobalConfiguration findByKey(final String iKey) { + for (OGlobalConfiguration v : values()) { + if (v.getKey().equalsIgnoreCase(iKey)) + return v; + } + return null; + } + + /** + * Changes the configuration values in one shot by passing a Map of values. Keys can be the Java ENUM names or the string + * representation of configuration values + */ + public static void setConfiguration(final Map iConfig) { + for (Entry config : iConfig.entrySet()) { + for (OGlobalConfiguration v : values()) { + if (v.getKey().equals(config.getKey())) { + v.setValue(config.getValue()); + break; + } else if (v.name().equals(config.getKey())) { + v.setValue(config.getValue()); + break; + } + } + } + } + + /** + * Assign configuration values by reading system properties. + */ + private static void readConfiguration() { + String prop; + for (OGlobalConfiguration config : values()) { + prop = System.getProperty(config.key); + if (prop != null) + config.setValue(prop); + } + } + + public T getValue() { + //noinspection unchecked + return (T) (value != null ? value : defValue); + } + + public void setValue(final Object iValue) { + Object oldValue = value; + + if (iValue != null) + if (type == Boolean.class) + value = Boolean.parseBoolean(iValue.toString()); + else if (type == Integer.class) + value = Integer.parseInt(iValue.toString()); + else if (type == Float.class) + value = Float.parseFloat(iValue.toString()); + else if (type == String.class) + value = iValue.toString(); + else if (type.isEnum()) { + boolean accepted = false; + + if (type.isInstance(iValue)) { + value = iValue; + accepted = true; + } else if (iValue instanceof String) { + final String string = (String) iValue; + + for (Object constant : type.getEnumConstants()) { + final Enum enumConstant = (Enum) constant; + + if (enumConstant.name().equalsIgnoreCase(string)) { + value = enumConstant; + accepted = true; + break; + } + } + } + + if (!accepted) + throw new IllegalArgumentException("Invalid value of `" + key + "` option."); + } else + value = iValue; + + if (changeCallback != null) { + try { + changeCallback.change(oldValue, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public boolean getValueAsBoolean() { + final Object v = value != null ? value : defValue; + return v instanceof Boolean ? (Boolean) v : Boolean.parseBoolean(v.toString()); + } + + public String getValueAsString() { + return value != null ? value.toString() : defValue != null ? defValue.toString() : null; + } + + public int getValueAsInteger() { + final Object v = value != null ? value : defValue; + return (int) (v instanceof Number ? ((Number) v).intValue() : OFileUtils.getSizeAsNumber(v.toString())); + } + + public long getValueAsLong() { + final Object v = value != null ? value : defValue; + return v instanceof Number ? ((Number) v).longValue() : OFileUtils.getSizeAsNumber(v.toString()); + } + + public float getValueAsFloat() { + final Object v = value != null ? value : defValue; + return v instanceof Float ? (Float) v : Float.parseFloat(v.toString()); + } + + public String getKey() { + return key; + } + + public Boolean isChangeableAtRuntime() { + return canChangeAtRuntime; + } + + public boolean isHidden() { + return hidden; + } + + public Object getDefValue() { + return defValue; + } + + public Class getType() { + return type; + } + + public String getDescription() { + return description; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterConfiguration.java new file mode 100644 index 00000000000..aed295e6f74 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterConfiguration.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +public interface OStorageClusterConfiguration { + + enum STATUS { + ONLINE, OFFLINE + } + + int getId(); + + String getName(); + + String getLocation(); + + int getDataSegmentId(); + + STATUS getStatus(); + + void setStatus(STATUS iStatus); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterHoleConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterHoleConfiguration.java new file mode 100644 index 00000000000..8a407d71296 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageClusterHoleConfiguration.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +public class OStorageClusterHoleConfiguration extends OStorageFileConfiguration { + private static final long serialVersionUID = 1L; + + private static final String DEF_EXTENSION = ".och"; + private static final String DEF_INCREMENT_SIZE = "50%"; + + public OStorageClusterHoleConfiguration() { + super(); + } + + public OStorageClusterHoleConfiguration(OStorageSegmentConfiguration iParent, String iPath, String iType, String iMaxSize) { + super(iParent, iPath + DEF_EXTENSION, iType, iMaxSize, DEF_INCREMENT_SIZE); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageConfiguration.java new file mode 100755 index 00000000000..278b7cae05a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageConfiguration.java @@ -0,0 +1,1076 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategy; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategyFactory; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.id.OImmutableRecordId; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.ORoundRobinClusterSelectionStrategy; +import com.orientechnologies.orient.core.record.impl.OBlob; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.sql.parser.OStatement; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Versions: + *
        + *
      • 3 = introduced file directory in physical segments and data-segment id in clusters
      • + *
      • 4 = ??
      • + *
      • 5 = ??
      • + *
      • 6 = ??
      • + *
      • 7 = ??
      • + *
      • 8 = introduced cluster selection strategy as string
      • + *
      • 9 = introduced minimumclusters as string
      • + *
      • 12 = introduced record conflict strategy as string in both storage and paginated clusters
      • + *
      • 13 = introduced cluster status to manage cluster as "offline" with the new command "alter cluster status offline". Removed + * data segments
      • + *
      • 14 = no changes, but version was incremented
      • + *
      • 15 = introduced encryption and encryptionKey
      • + *
      + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("serial") +public class OStorageConfiguration implements OSerializableStream { + public static final ORecordId CONFIG_RID = new OImmutableRecordId(0, 0); + + public static final String DEFAULT_CHARSET = "UTF-8"; + public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; + public static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + private String charset; + public static final int CURRENT_VERSION = 17; + public static final int CURRENT_BINARY_FORMAT_VERSION = 13; + private final List properties = new ArrayList(); + protected final transient OStorage storage; + private volatile OContextConfiguration configuration; + public volatile int version; + public volatile String name; + public volatile String schemaRecordId; + public volatile String dictionaryRecordId; + public volatile String indexMgrRecordId; + public volatile String dateFormat; + public volatile String dateTimeFormat; + public volatile int binaryFormatVersion; + public volatile OStorageSegmentConfiguration fileTemplate; + public volatile List clusters; + private volatile String localeLanguage; + private volatile String localeCountry; + private volatile TimeZone timeZone; + private transient volatile Locale localeInstance; + private transient volatile DecimalFormatSymbols unusualSymbols; + private volatile String clusterSelection; + private volatile String conflictStrategy; + private volatile String recordSerializer; + private volatile int recordSerializerVersion; + private volatile boolean strictSQL; + private volatile Map loadProperties; + private volatile ConcurrentMap indexEngines; + private volatile transient boolean validation = true; + private volatile boolean txRequiredForSQLGraphOperations; + + protected final Charset streamCharset; + + public OStorageConfiguration(final OStorage iStorage, Charset streamCharset) { + storage = iStorage; + this.streamCharset = streamCharset; + + initConfiguration(); + clear(); + } + + public void initConfiguration() { + configuration = new OContextConfiguration(); + } + + public void clear() { + fileTemplate = new OStorageSegmentConfiguration(); + + charset = DEFAULT_CHARSET; + synchronized (properties) { + properties.clear(); + } + + version = -1; + name = null; + schemaRecordId = null; + dictionaryRecordId = null; + indexMgrRecordId = null; + dateFormat = DEFAULT_DATE_FORMAT; + dateTimeFormat = DEFAULT_DATETIME_FORMAT; + binaryFormatVersion = 0; + clusters = Collections.synchronizedList(new ArrayList()); + localeLanguage = Locale.getDefault().getLanguage(); + localeCountry = Locale.getDefault().getCountry(); + timeZone = TimeZone.getDefault(); + localeInstance = null; + unusualSymbols = null; + clusterSelection = null; + conflictStrategy = null; + + getContextConfiguration().setValue(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS, + OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS.getValueAsInteger()); // 0 = AUTOMATIC + + autoInitClusters(); + + recordSerializer = null; + recordSerializerVersion = 0; + strictSQL = false; + txRequiredForSQLGraphOperations = true; + indexEngines = new ConcurrentHashMap(); + validation = OGlobalConfiguration.DB_VALIDATION.getValueAsBoolean(); + + binaryFormatVersion = CURRENT_BINARY_FORMAT_VERSION; + + txRequiredForSQLGraphOperations = OGlobalConfiguration.SQL_GRAPH_CONSISTENCY_MODE.getValueAsString().equalsIgnoreCase("tx"); + } + + private void autoInitClusters() { + if (getContextConfiguration().getValueAsInteger(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS) == 0) { + final int cpus = Runtime.getRuntime().availableProcessors(); + getContextConfiguration().setValue(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS, cpus > 64 ? 64 : cpus); + } + } + + public String getConflictStrategy() { + return conflictStrategy; + } + + public void setConflictStrategy(String conflictStrategy) { + this.conflictStrategy = conflictStrategy; + } + + public OContextConfiguration getContextConfiguration() { + return configuration; + } + + /** + * This method load the record information by the internal cluster segment. It's for compatibility with older database than + * 0.9.25. + * + * @param iProperties + * + * @return + * + * @throws OSerializationException + * @compatibility 0.9.25 + */ + public OStorageConfiguration load(final Map iProperties) throws OSerializationException { + initConfiguration(); + + final String compressionMethod = (String) iProperties + .get(OGlobalConfiguration.STORAGE_COMPRESSION_METHOD.getKey().toLowerCase(Locale.ENGLISH)); + if (compressionMethod != null) + // SAVE COMPRESSION METHOD IN CONFIGURATION + configuration.setValue(OGlobalConfiguration.STORAGE_COMPRESSION_METHOD, compressionMethod); + + final String encryptionMethod = (String) iProperties + .get(OGlobalConfiguration.STORAGE_ENCRYPTION_METHOD.getKey().toLowerCase(Locale.ENGLISH)); + if (encryptionMethod != null) + // SAVE ENCRYPTION METHOD IN CONFIGURATION + configuration.setValue(OGlobalConfiguration.STORAGE_ENCRYPTION_METHOD, encryptionMethod); + + final String encryptionKey = (String) iProperties + .get(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY.getKey().toLowerCase(Locale.ENGLISH)); + if (encryptionKey != null) + // SAVE ENCRYPTION KEY IN CONFIGURATION + configuration.setValue(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY, encryptionKey); + + final byte[] record = storage.readRecord(CONFIG_RID, null, false, false, null).getResult().buffer; + + if (record == null) + throw new OStorageException("Cannot load database configuration. The database seems corrupted"); + + fromStream(record, 0, record.length, streamCharset); + + this.loadProperties = new HashMap(iProperties); + + return this; + } + + public Map getLoadProperties() { + if (loadProperties == null) + return Collections.emptyMap(); + + return Collections.unmodifiableMap(loadProperties); + } + + public void update() throws OSerializationException { + final byte[] record = toStream(streamCharset); + storage.updateRecord(CONFIG_RID, true, record, -1, OBlob.RECORD_TYPE, 0, null); + } + + public boolean isEmpty() { + return clusters.isEmpty(); + } + + public String getDirectory() { + return fileTemplate.location != null ? fileTemplate.getLocation() : ((OLocalPaginatedStorage) storage).getStoragePath(); + } + + public Locale getLocaleInstance() { + if (localeInstance == null) + localeInstance = new Locale(localeLanguage, localeCountry); + + return localeInstance; + } + + public void resetLocaleInstance() { + localeInstance = null; + } + + public SimpleDateFormat getDateFormatInstance() { + final SimpleDateFormat dateFormatInstance = new SimpleDateFormat(dateFormat); + dateFormatInstance.setLenient(false); + dateFormatInstance.setTimeZone(timeZone); + return dateFormatInstance; + } + + public SimpleDateFormat getDateTimeFormatInstance() { + final SimpleDateFormat dateTimeFormatInstance = new SimpleDateFormat(dateTimeFormat); + dateTimeFormatInstance.setLenient(false); + dateTimeFormatInstance.setTimeZone(timeZone); + return dateTimeFormatInstance; + } + + public DecimalFormatSymbols getUnusualSymbols() { + if (unusualSymbols == null) + unusualSymbols = new DecimalFormatSymbols(getLocaleInstance()); + return unusualSymbols; + } + + public void fromStream(final byte[] stream, int offset, int length, Charset charset) { + clear(); + + final String[] values = new String(stream, offset, length, charset).split("\\|"); + int index = 0; + version = Integer.parseInt(read(values[index++])); + + name = read(values[index++]); + + schemaRecordId = read(values[index++]); + dictionaryRecordId = read(values[index++]); + + if (version > 0) + indexMgrRecordId = read(values[index++]); + else + // @COMPATIBILITY + indexMgrRecordId = null; + + localeLanguage = read(values[index++]); + localeCountry = read(values[index++]); + + //@COMPATIBILIY with 2.1 version, in this version locale was not mandatory + if (localeLanguage == null || localeCountry == null) { + final Locale locale = Locale.getDefault(); + + if (localeLanguage == null) + OLogManager.instance().warn(this, + "Information about storage locale is undefined (language is undefined) default locale " + locale + " will be used"); + + if (localeCountry == null) + OLogManager.instance().warn(this, + "Information about storage locale is undefined (country is undefined) default locale " + locale + " will be used"); + } + + dateFormat = read(values[index++]); + dateTimeFormat = read(values[index++]); + + // @COMPATIBILITY 1.2.0 + if (version >= 4) { + timeZone = TimeZone.getTimeZone(read(values[index++])); + this.charset = read(values[index++]); + } + + final ORecordConflictStrategyFactory conflictStrategyFactory = Orient.instance().getRecordConflictStrategy(); + if (version >= 12) { + ORecordConflictStrategy strategy = conflictStrategyFactory.getStrategy(read(values[index++])); + conflictStrategy = strategy == null ? conflictStrategyFactory.getDefaultStrategy() : strategy.getName(); + } else + conflictStrategy = conflictStrategyFactory.getDefaultStrategy(); + + // @COMPATIBILITY + if (version > 1) + index = phySegmentFromStream(values, index, fileTemplate); + + int size = Integer.parseInt(read(values[index++])); + + // PREPARE THE LIST OF CLUSTERS + clusters.clear(); + + String determineStorageCompression = null; + + for (int i = 0; i < size; ++i) { + final int clusterId = Integer.parseInt(read(values[index++])); + + if (clusterId == -1) + continue; + + final String clusterName = read(values[index++]); + final int targetDataSegmentId = version >= 3 ? Integer.parseInt(read(values[index++])) : 0; + + final String clusterType = read(values[index++]); + + final OStorageClusterConfiguration currentCluster; + + if (clusterType.equals("d")) { + final boolean cc = Boolean.valueOf(read(values[index++])); + final float bb = Float.valueOf(read(values[index++])); + final float aa = Float.valueOf(read(values[index++])); + final String clusterCompression = read(values[index++]); + + if (determineStorageCompression == null) + // TRY TO DETERMINE THE STORAGE COMPRESSION. BEFORE VERSION 11 IT WASN'T STORED IN STORAGE CFG, SO GET FROM THE FIRST + // CLUSTER + determineStorageCompression = clusterCompression; + + String clusterEncryption = null; + if (version >= 15) + clusterEncryption = read(values[index++]); + + final String clusterConflictStrategy; + if (version >= 12) + clusterConflictStrategy = read(values[index++]); + else + // INHERIT THE STRATEGY IN STORAGE + clusterConflictStrategy = null; + + OStorageClusterConfiguration.STATUS status = OStorageClusterConfiguration.STATUS.ONLINE; + if (version >= 13) + status = OStorageClusterConfiguration.STATUS.valueOf(read(values[index++])); + + currentCluster = new OStoragePaginatedClusterConfiguration(this, clusterId, clusterName, null, cc, bb, aa, + clusterCompression, clusterEncryption, configuration.getValueAsString(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY), + clusterConflictStrategy, status); + + } else if (clusterType.equals("p")) + // PHYSICAL CLUSTER + throw new IllegalArgumentException("Cluster of storage 'local' are not supported since 2.0"); + else + throw new IllegalArgumentException("Unsupported cluster type: " + clusterType); + + // MAKE ROOMS, EVENTUALLY FILLING EMPTIES ENTRIES + for (int c = clusters.size(); c <= clusterId; ++c) + clusters.add(null); + + clusters.set(clusterId, currentCluster); + } + + if (version < 13) { + // OLD: READ DATA-SEGMENTS + size = Integer.parseInt(read(values[index++])); + + for (int i = 0; i < size; ++i) { + int dataId = Integer.parseInt(read(values[index++])); + if (dataId == -1) + continue; + read(values[index++]); + read(values[index++]); + read(values[index++]); + read(values[index++]); + } + + // READ TX_SEGMENT STUFF + read(values[index++]); + read(values[index++]); + read(values[index++]); + read(values[index++]); + read(values[index++]); + } + + size = Integer.parseInt(read(values[index++])); + clearProperties(); + for (int i = 0; i < size; ++i) + setProperty(read(values[index++]), read(values[index++])); + + if (version >= 7) + binaryFormatVersion = Integer.parseInt(read(values[index++])); + else if (version == 6) + binaryFormatVersion = 9; + else + binaryFormatVersion = 8; + + if (version >= 8) + clusterSelection = read(values[index++]); + else + // DEFAULT = ROUND-ROBIN + clusterSelection = ORoundRobinClusterSelectionStrategy.NAME; + + if (version >= 9) + setMinimumClusters(Integer.parseInt(read(values[index++]))); + else + // DEFAULT = 1 + setMinimumClusters(1); + + autoInitClusters(); + + if (version >= 10) { + recordSerializer = read(values[index++]); + recordSerializerVersion = Integer.parseInt(read(values[index++])); + } + + if (version >= 11) { + // READ THE CONFIGURATION + final int cfgSize = Integer.parseInt(read(values[index++])); + for (int i = 0; i < cfgSize; ++i) { + final String key = read(values[index++]); + final Object value = read(values[index++]); + + final OGlobalConfiguration cfg = OGlobalConfiguration.findByKey(key); + if (cfg != null) { + if (value != null) + configuration.setValue(key, OType.convert(value, cfg.getType())); + } else + OLogManager.instance().warn(this, "Ignored storage configuration because not supported: %s=%s", key, value); + } + } else + // SAVE STORAGE COMPRESSION METHOD AS PROPERTY + configuration.setValue(OGlobalConfiguration.STORAGE_COMPRESSION_METHOD, determineStorageCompression); + + if (version > 15) { + final int enginesSize = Integer.parseInt(read(values[index++])); + + for (int i = 0; i < enginesSize; i++) { + final String name = read(values[index++]); + final String algorithm = read(values[index++]); + final String indexType; + + if (version > 16) + indexType = read(values[index++]); + else + indexType = ""; + + final byte valueSerializerId = Byte.parseByte(read(values[index++])); + final byte keySerializerId = Byte.parseByte(read(values[index++])); + + final boolean isAutomatic = Boolean.parseBoolean(read((values[index++]))); + final Boolean durableInNonTxMode; + + if (read(values[index]) == null) { + durableInNonTxMode = null; + index++; + } else + durableInNonTxMode = Boolean.parseBoolean(read(values[index++])); + + final int version = Integer.parseInt(read(values[index++])); + final boolean nullValuesSupport = Boolean.parseBoolean(read((values[index++]))); + final int keySize = Integer.parseInt(read(values[index++])); + + final int typesLength = Integer.parseInt(read(values[index++])); + final OType[] types = new OType[typesLength]; + + for (int n = 0; n < types.length; n++) { + final OType type = OType.valueOf(read(values[index++])); + types[n] = type; + } + + final int propertiesSize = Integer.parseInt(read(values[index++])); + final Map engineProperties; + if (propertiesSize == 0) + engineProperties = null; + else { + engineProperties = new HashMap(propertiesSize); + for (int n = 0; n < propertiesSize; n++) { + final String key = read(values[index++]); + final String value = read(values[index++]); + engineProperties.put(key, value); + } + } + + final IndexEngineData indexEngineData = new IndexEngineData(name, algorithm, indexType, durableInNonTxMode, version, + valueSerializerId, keySerializerId, isAutomatic, types, nullValuesSupport, keySize, engineProperties); + + indexEngines.put(name.toLowerCase(getLocaleInstance()), indexEngineData); + } + } + } + + /** + * @deprecated because method uses native encoding use {@link #fromStream(byte[], int, int, Charset)} instead. + */ + @Deprecated + public OSerializableStream fromStream(final byte[] iStream) throws OSerializationException { + fromStream(iStream, 0, iStream.length, Charset.defaultCharset()); + return this; + } + + /** + * @deprecated because method uses native encoding use {@link #toStream(Charset)} instead. + */ + @Deprecated + public byte[] toStream() throws OSerializationException { + return toStream(Integer.MAX_VALUE, Charset.defaultCharset()); + } + + public byte[] toStream(Charset charset) { + return toStream(Integer.MAX_VALUE, charset); + } + + public byte[] toStream(final int iNetworkVersion, Charset charset) throws OSerializationException { + final StringBuilder buffer = new StringBuilder(8192); + + write(buffer, CURRENT_VERSION); + write(buffer, name); + + write(buffer, schemaRecordId); + write(buffer, dictionaryRecordId); + write(buffer, indexMgrRecordId); + + write(buffer, localeLanguage); + write(buffer, localeCountry); + write(buffer, dateFormat); + write(buffer, dateTimeFormat); + + write(buffer, timeZone.getID()); + write(buffer, this.charset); + if (iNetworkVersion > 24) + write(buffer, conflictStrategy); + + phySegmentToStream(buffer, fileTemplate); + + write(buffer, clusters.size()); + for (OStorageClusterConfiguration c : clusters) { + if (c == null) { + write(buffer, -1); + continue; + } + + write(buffer, c.getId()); + write(buffer, c.getName()); + write(buffer, c.getDataSegmentId()); + + if (c instanceof OStoragePaginatedClusterConfiguration) { + write(buffer, "d"); + + final OStoragePaginatedClusterConfiguration paginatedClusterConfiguration = (OStoragePaginatedClusterConfiguration) c; + + write(buffer, paginatedClusterConfiguration.useWal); + write(buffer, paginatedClusterConfiguration.recordOverflowGrowFactor); + write(buffer, paginatedClusterConfiguration.recordGrowFactor); + write(buffer, paginatedClusterConfiguration.compression); + if (iNetworkVersion >= 31) + write(buffer, paginatedClusterConfiguration.encryption); + if (iNetworkVersion > 24) + write(buffer, paginatedClusterConfiguration.conflictStrategy); + if (iNetworkVersion > 25) + write(buffer, paginatedClusterConfiguration.getStatus().name().toString()); + } + } + if (iNetworkVersion <= 25) { + // dataSegment array + write(buffer, 0); + // tx Segment File + write(buffer, ""); + write(buffer, ""); + write(buffer, 0); + // tx segment flags + write(buffer, false); + write(buffer, false); + } + synchronized (properties) { + write(buffer, properties.size()); + for (OStorageEntryConfiguration e : properties) + entryToStream(buffer, e); + } + + write(buffer, binaryFormatVersion); + write(buffer, clusterSelection); + write(buffer, getMinimumClusters()); + + if (iNetworkVersion > 24) { + write(buffer, recordSerializer); + write(buffer, recordSerializerVersion); + + // WRITE CONFIGURATION + write(buffer, configuration.getContextSize()); + for (String k : configuration.getContextKeys()) { + final OGlobalConfiguration cfg = OGlobalConfiguration.findByKey(k); + + write(buffer, k); + write(buffer, cfg.isHidden() ? null : configuration.getValueAsString(cfg)); + } + } + + write(buffer, indexEngines.size()); + for (IndexEngineData engineData : indexEngines.values()) { + write(buffer, engineData.name); + write(buffer, engineData.algorithm); + write(buffer, engineData.indexType == null ? "" : engineData.indexType); + + write(buffer, engineData.valueSerializerId); + write(buffer, engineData.keySerializedId); + + write(buffer, engineData.isAutomatic); + write(buffer, engineData.durableInNonTxMode); + + write(buffer, engineData.version); + write(buffer, engineData.nullValuesSupport); + write(buffer, engineData.keySize); + + if (engineData.keyTypes != null) { + write(buffer, engineData.keyTypes.length); + for (OType type : engineData.keyTypes) { + write(buffer, type.name()); + } + } else { + write(buffer, 0); + } + + if (engineData.engineProperties == null) { + write(buffer, 0); + } else { + write(buffer, engineData.engineProperties.size()); + for (Map.Entry property : engineData.engineProperties.entrySet()) { + write(buffer, property.getKey()); + write(buffer, property.getValue()); + } + } + } + + // PLAIN: ALLOCATE ENOUGH SPACE TO REUSE IT EVERY TIME + buffer.append("|"); + + return buffer.toString().getBytes(charset); + } + + public void lock() throws IOException { + } + + public void unlock() throws IOException { + } + + public void create() throws IOException { + storage.createRecord(CONFIG_RID, new byte[] { 0, 0, 0, 0 }, 0, OBlob.RECORD_TYPE, (byte) 0, null); + } + + public void synch() throws IOException { + } + + public void setSoftlyClosed(boolean softlyClosed) throws IOException { + } + + public void delete() throws IOException { + close(); + } + + public void close() throws IOException { + clear(); + initConfiguration(); + } + + public void dropCluster(final int iClusterId) { + if (iClusterId < clusters.size()) { + clusters.set(iClusterId, null); + update(); + } + } + + public void addIndexEngine(String name, IndexEngineData engineData) { + final IndexEngineData oldEngine = indexEngines.putIfAbsent(name, engineData); + + if (oldEngine != null) + OLogManager.instance() + .warn(this, "Index engine with name '" + engineData.name + "' already contained in database configuration"); + + update(); + } + + public void deleteIndexEngine(String name) { + indexEngines.remove(name); + update(); + } + + public Set indexEngines() { + return Collections.unmodifiableSet(indexEngines.keySet()); + } + + public IndexEngineData getIndexEngine(String name) { + return indexEngines.get(name); + } + + public void setClusterStatus(final int clusterId, final OStorageClusterConfiguration.STATUS iStatus) { + final OStorageClusterConfiguration clusterCfg = clusters.get(clusterId); + if (clusterCfg != null) + clusterCfg.setStatus(iStatus); + update(); + } + + public TimeZone getTimeZone() { + return timeZone; + } + + public void setTimeZone(final TimeZone timeZone) { + this.timeZone = timeZone; + } + + public String getLocaleLanguage() { + return localeLanguage; + } + + public void setLocaleLanguage(final String iValue) { + localeLanguage = iValue; + localeInstance = null; + } + + public String getLocaleCountry() { + return localeCountry; + } + + public void setLocaleCountry(final String iValue) { + localeCountry = iValue; + localeInstance = null; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public String getDateFormat() { + return dateFormat; + } + + public String getDateTimeFormat() { + return dateTimeFormat; + } + + public String getClusterSelection() { + return clusterSelection; + } + + public void setClusterSelection(final String clusterSelection) { + this.clusterSelection = clusterSelection; + } + + public int getMinimumClusters() { + final int mc = getContextConfiguration().getValueAsInteger(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS); + if (mc == 0) { + autoInitClusters(); + return (Integer) getContextConfiguration().getValue(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS); + } + return mc; + } + + public void setMinimumClusters(final int minimumClusters) { + getContextConfiguration().setValue(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS, minimumClusters); + autoInitClusters(); + } + + public String getRecordSerializer() { + return recordSerializer; + } + + public void setRecordSerializer(String recordSerializer) { + this.recordSerializer = recordSerializer; + } + + public int getRecordSerializerVersion() { + return recordSerializerVersion; + } + + public void setRecordSerializerVersion(int recordSerializerVersion) { + this.recordSerializerVersion = recordSerializerVersion; + } + + public boolean isStrictSql() { + return strictSQL; + } + + public boolean isTxRequiredForSQLGraphOperations() { + return txRequiredForSQLGraphOperations; + } + + public List getProperties() { + return Collections.unmodifiableList(properties); + } + + public void setProperty(final String iName, final String iValue) { + if (OStatement.CUSTOM_STRICT_SQL.equalsIgnoreCase(iName)) + // SET STRICT SQL VARIABLE + strictSQL = "true".equalsIgnoreCase(iValue); + + if ("txRequiredForSQLGraphOperations".equalsIgnoreCase(iName)) + // SET TX SQL GRAPH OPERATIONS + txRequiredForSQLGraphOperations = "true".equalsIgnoreCase(iValue); + + if ("txRequiredForSQLGraphOperations".equalsIgnoreCase(iName)) + // SET TX SQL GRAPH OPERATIONS + txRequiredForSQLGraphOperations = "true".equalsIgnoreCase(iValue); + + if ("validation".equalsIgnoreCase(iName)) + validation = "true".equalsIgnoreCase(iValue); + + synchronized (properties) { + for (Iterator it = properties.iterator(); it.hasNext(); ) { + final OStorageEntryConfiguration e = it.next(); + if (e.name.equalsIgnoreCase(iName)) { + // FOUND: OVERWRITE IT + e.value = iValue; + return; + } + } + + // NOT FOUND: CREATE IT + properties.add(new OStorageEntryConfiguration(iName, iValue)); + } + } + + public String getProperty(final String iName) { + synchronized (properties) { + for (Iterator it = properties.iterator(); it.hasNext(); ) { + final OStorageEntryConfiguration e = it.next(); + if (e.name.equalsIgnoreCase(iName)) + return e.value; + } + return null; + } + } + + public boolean existsProperty(final String iName) { + synchronized (properties) { + for (Iterator it = properties.iterator(); it.hasNext(); ) { + final OStorageEntryConfiguration e = it.next(); + if (e.name.equalsIgnoreCase(iName)) + return true; + } + return false; + } + } + + public void removeProperty(final String iName) { + synchronized (properties) { + for (Iterator it = properties.iterator(); it.hasNext(); ) { + final OStorageEntryConfiguration e = it.next(); + if (e.name.equalsIgnoreCase(iName)) { + it.remove(); + break; + } + } + } + } + + public void clearProperties() { + synchronized (properties) { + properties.clear(); + } + } + + public boolean isValidationEnabled() { + return validation; + } + + public void setValidation(final boolean validation) { + setProperty("validation", validation ? "true" : "false"); + } + + protected void bindPropertiesToContext(final Map iProperties) { + final String compressionMethod = iProperties != null ? + (String) iProperties.get(OGlobalConfiguration.STORAGE_COMPRESSION_METHOD.getKey().toLowerCase(Locale.ENGLISH)) : + null; + if (compressionMethod != null) + // SAVE COMPRESSION METHOD IN CONFIGURATION + getContextConfiguration().setValue(OGlobalConfiguration.STORAGE_COMPRESSION_METHOD, compressionMethod); + + final String encryptionMethod = iProperties != null ? + (String) iProperties.get(OGlobalConfiguration.STORAGE_ENCRYPTION_METHOD.getKey().toLowerCase(Locale.ENGLISH)) : + null; + if (encryptionMethod != null) + // SAVE ENCRYPTION METHOD IN CONFIGURATION + getContextConfiguration().setValue(OGlobalConfiguration.STORAGE_ENCRYPTION_METHOD, encryptionMethod); + + final String encryptionKey = iProperties != null ? + (String) iProperties.get(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY.getKey().toLowerCase(Locale.ENGLISH)) : + null; + if (encryptionKey != null) + // SAVE ENCRYPTION KEY IN CONFIGURATION + getContextConfiguration().setValue(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY, encryptionKey); + } + + private int phySegmentFromStream(final String[] values, int index, final OStorageSegmentConfiguration iSegment) { + iSegment.location = version > 2 ? read(values[index++]) : null; + iSegment.maxSize = read(values[index++]); + iSegment.fileType = read(values[index++]); + iSegment.fileStartSize = read(values[index++]); + iSegment.fileMaxSize = read(values[index++]); + iSegment.fileIncrementSize = read(values[index++]); + iSegment.defrag = read(values[index++]); + + final int size = Integer.parseInt(read(values[index++])); + iSegment.infoFiles = new OStorageFileConfiguration[size]; + String fileName; + for (int i = 0; i < size; ++i) { + fileName = read(values[index++]); + + if (!fileName.contains("$")) { + // @COMPATIBILITY 0.9.25 + int pos = fileName.indexOf("/databases"); + if (pos > -1) { + fileName = "${" + Orient.ORIENTDB_HOME + "}" + fileName.substring(pos); + } + } + + iSegment.infoFiles[i] = new OStorageFileConfiguration(iSegment, fileName, read(values[index++]), read(values[index++]), + iSegment.fileIncrementSize); + } + + return index; + } + + private void phySegmentToStream(final StringBuilder iBuffer, final OStorageSegmentConfiguration iSegment) { + write(iBuffer, iSegment.location); + write(iBuffer, iSegment.maxSize); + write(iBuffer, iSegment.fileType); + write(iBuffer, iSegment.fileStartSize); + write(iBuffer, iSegment.fileMaxSize); + write(iBuffer, iSegment.fileIncrementSize); + write(iBuffer, iSegment.defrag); + + write(iBuffer, iSegment.infoFiles.length); + for (OStorageFileConfiguration f : iSegment.infoFiles) + fileToStream(iBuffer, f); + } + + private void fileToStream(final StringBuilder iBuffer, final OStorageFileConfiguration iFile) { + write(iBuffer, iFile.path); + write(iBuffer, iFile.type); + write(iBuffer, iFile.maxSize); + } + + private void entryToStream(final StringBuilder iBuffer, final OStorageEntryConfiguration iEntry) { + write(iBuffer, iEntry.name); + write(iBuffer, iEntry.value); + } + + private String read(final String iValue) { + if (iValue.equals(" ")) + return null; + return iValue; + } + + private void write(final StringBuilder iBuffer, final Object iValue) { + if (iBuffer.length() > 0) + iBuffer.append('|'); + iBuffer.append(iValue != null ? iValue.toString() : ' '); + } + + public static final class IndexEngineData { + private final String name; + private final String algorithm; + private final String indexType; + private final Boolean durableInNonTxMode; + private final int version; + private final byte valueSerializerId; + private final byte keySerializedId; + private final boolean isAutomatic; + private final OType[] keyTypes; + private final boolean nullValuesSupport; + private final int keySize; + private final Map engineProperties; + + public IndexEngineData(final String name, final String algorithm, String indexType, final Boolean durableInNonTxMode, + final int version, final byte valueSerializerId, final byte keySerializedId, final boolean isAutomatic, + final OType[] keyTypes, final boolean nullValuesSupport, final int keySize, final Map engineProperties) { + this.name = name; + this.algorithm = algorithm; + this.indexType = indexType; + this.durableInNonTxMode = durableInNonTxMode; + this.version = version; + this.valueSerializerId = valueSerializerId; + this.keySerializedId = keySerializedId; + this.isAutomatic = isAutomatic; + this.keyTypes = keyTypes; + this.nullValuesSupport = nullValuesSupport; + this.keySize = keySize; + if (engineProperties == null) + this.engineProperties = null; + else + this.engineProperties = new HashMap(engineProperties); + } + + public int getKeySize() { + return keySize; + } + + public String getName() { + return name; + } + + public String getAlgorithm() { + return algorithm; + } + + public Boolean getDurableInNonTxMode() { + return durableInNonTxMode; + } + + public int getVersion() { + return version; + } + + public byte getValueSerializerId() { + return valueSerializerId; + } + + public byte getKeySerializedId() { + return keySerializedId; + } + + public boolean isAutomatic() { + return isAutomatic; + } + + public OType[] getKeyTypes() { + return keyTypes; + } + + public boolean isNullValuesSupport() { + return nullValuesSupport; + } + + public Map getEngineProperties() { + if (engineProperties == null) + return null; + + return Collections.unmodifiableMap(engineProperties); + } + + public String getIndexType() { + return indexType; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageDataHoleConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageDataHoleConfiguration.java new file mode 100644 index 00000000000..e34bac491c0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageDataHoleConfiguration.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +public class OStorageDataHoleConfiguration extends OStorageFileConfiguration { + private static final long serialVersionUID = 1L; + + private static final String DEF_EXTENSION = ".odh"; + private static final String DEF_INCREMENT_SIZE = "50%"; + + public OStorageDataHoleConfiguration() { + } + + public OStorageDataHoleConfiguration(OStorageSegmentConfiguration iParent, String iPath, String iType, String iMaxSize) { + super(iParent, iPath + DEF_EXTENSION, iType, iMaxSize, DEF_INCREMENT_SIZE); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageEntryConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageEntryConfiguration.java new file mode 100644 index 00000000000..2e95b596e98 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageEntryConfiguration.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +import java.io.Serializable; + +@SuppressWarnings("serial") +public class OStorageEntryConfiguration implements Serializable { + public volatile String name; + public volatile String value; + + public OStorageEntryConfiguration() { + } + + public OStorageEntryConfiguration(final String iName, final String iValue) { + name = iName; + value = iValue; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageFileConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageFileConfiguration.java new file mode 100644 index 00000000000..ae4f73f1a48 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageFileConfiguration.java @@ -0,0 +1,50 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +import java.io.Serializable; + +@SuppressWarnings("serial") +public class OStorageFileConfiguration implements Serializable { + + public transient OStorageSegmentConfiguration parent; + + public String path; + public String type = "mmap"; + public String maxSize = null; + public String incrementSize = "50%"; + + public OStorageFileConfiguration() { + } + + public OStorageFileConfiguration(final OStorageSegmentConfiguration iParent, final String iPath, final String iType, + final String iMaxSize, String iIncrementSize) { + parent = iParent; + path = iPath; + type = iType; + maxSize = iMaxSize; + incrementSize = iIncrementSize; + } + + @Override + public String toString() { + return path; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStoragePaginatedClusterConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStoragePaginatedClusterConfiguration.java new file mode 100755 index 00000000000..c7a9217cf35 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStoragePaginatedClusterConfiguration.java @@ -0,0 +1,89 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.config; + +/** + * @author Andrey Lomakin + * @since 09.07.13 + */ +public class OStoragePaginatedClusterConfiguration implements OStorageClusterConfiguration { + public static final float DEFAULT_GROW_FACTOR = (float) 1.2; + public float recordOverflowGrowFactor = DEFAULT_GROW_FACTOR; + public float recordGrowFactor = DEFAULT_GROW_FACTOR; + public String compression; + public String encryption; + public String encryptionKey; + public transient OStorageConfiguration root; + public int id; + public String name; + public String location; + public boolean useWal = true; + public String conflictStrategy; + private STATUS status = STATUS.ONLINE; + + public OStoragePaginatedClusterConfiguration(final OStorageConfiguration root, final int id, final String name, + final String location, final boolean useWal, final float recordOverflowGrowFactor, final float recordGrowFactor, + final String iCompression, final String iEncryption, final String iEncryptionKey, final String conflictStrategy, + final STATUS iStatus) { + this.root = root; + this.id = id; + this.name = name; + this.location = location; + this.useWal = useWal; + this.recordOverflowGrowFactor = recordOverflowGrowFactor; + this.recordGrowFactor = recordGrowFactor; + this.compression = iCompression; + this.encryption = iEncryption; + this.encryptionKey = iEncryptionKey; + this.conflictStrategy = conflictStrategy; + this.status = iStatus; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public int getDataSegmentId() { + return -1; + } + + @Override + public STATUS getStatus() { + return status; + } + + @Override + public void setStatus(final STATUS iStatus) { + status = iStatus; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/config/OStorageSegmentConfiguration.java b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageSegmentConfiguration.java new file mode 100755 index 00000000000..29feb7efd81 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/config/OStorageSegmentConfiguration.java @@ -0,0 +1,77 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.config; + +import java.io.Serializable; + +import com.orientechnologies.common.util.OCommonConst; + +@SuppressWarnings("serial") +public class OStorageSegmentConfiguration implements Serializable { + public transient OStorageConfiguration root; + public volatile int id; + public volatile String name; + public volatile String maxSize = "0"; + public volatile String fileType = "mmap"; + public volatile String fileStartSize = "500Kb"; + public volatile String fileMaxSize = "500Mb"; + public volatile String fileIncrementSize = "50%"; + public volatile String defrag = "auto"; + public volatile STATUS status = STATUS.ONLINE; + public OStorageFileConfiguration[] infoFiles; + String location; + + public enum STATUS { + ONLINE, OFFLINE + } + + public OStorageSegmentConfiguration() { + infoFiles = OCommonConst.EMPTY_FILE_CONFIGURATIONS_ARRAY; + } + + public OStorageSegmentConfiguration(final OStorageConfiguration iRoot, final String iSegmentName, final int iId) { + root = iRoot; + name = iSegmentName; + id = iId; + infoFiles = OCommonConst.EMPTY_FILE_CONFIGURATIONS_ARRAY; + } + + public OStorageSegmentConfiguration(final OStorageConfiguration iRoot, final String iSegmentName, final int iId, + final String iDirectory) { + root = iRoot; + name = iSegmentName; + id = iId; + location = iDirectory; + infoFiles = OCommonConst.EMPTY_FILE_CONFIGURATIONS_ARRAY; + } + + public void setRoot(OStorageConfiguration iRoot) { + this.root = iRoot; + for (OStorageFileConfiguration f : infoFiles) + f.parent = this; + } + + public String getLocation() { + if (location != null) + return location; + + return root != null ? root.getDirectory() : null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/conflict/OAutoMergeRecordConflictStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/conflict/OAutoMergeRecordConflictStrategy.java new file mode 100755 index 00000000000..f4aa3f660ec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/conflict/OAutoMergeRecordConflictStrategy.java @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.conflict; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSaveThreadLocal; +import com.orientechnologies.orient.core.storage.ORawBuffer; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.OStorageOperationResult; + +/** + * Auto merges new record with the existent. Collections are also merged, item by item. + * + * @author Luca Garulli + */ +public class OAutoMergeRecordConflictStrategy extends OVersionRecordConflictStrategy { + public static final String NAME = "automerge"; + + @Override + public byte[] onUpdate(OStorage storage, byte iRecordType, final ORecordId rid, final int iRecordVersion, + final byte[] iRecordContent, final AtomicInteger iDatabaseVersion) { + + if (iRecordType == ODocument.RECORD_TYPE) { + // No need lock, is already inside a lock. Use database to read temporary objects too + OStorageOperationResult res = storage.readRecord(rid, null, false, false, null); + final ODocument storedRecord = new ODocument(rid).fromStream(res.getResult().getBuffer()); + + ODocument newRecord = (ODocument) ORecordSaveThreadLocal.getLast(); + if (newRecord == null || !newRecord.getIdentity().equals(rid)) + newRecord = new ODocument(rid).fromStream(iRecordContent); + + storedRecord.merge(newRecord, true, true); + + iDatabaseVersion.set(Math.max(iDatabaseVersion.get(), iRecordVersion) + 1); + + return storedRecord.toStream(); + } else + // NO DOCUMENT, CANNOT MERGE SO RELY TO THE VERSION CHECK + checkVersions(rid, iRecordVersion, iDatabaseVersion.get()); + + return null; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/conflict/OContentRecordConflictStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/conflict/OContentRecordConflictStrategy.java new file mode 100755 index 00000000000..b7ce7f0c4d2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/conflict/OContentRecordConflictStrategy.java @@ -0,0 +1,78 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.conflict; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecordAbstract; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.storage.ORawBuffer; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.OStorageOperationResult; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Record conflict strategy that check the records content: if content is the same, se the higher version number. + * + * @author Luca Garulli + */ +public class OContentRecordConflictStrategy extends OVersionRecordConflictStrategy { + public static final String NAME = "content"; + + @Override + public byte[] onUpdate(OStorage storage, final byte iRecordType, final ORecordId rid, final int iRecordVersion, + final byte[] iRecordContent, final AtomicInteger iDatabaseVersion) { + + final boolean hasSameContent; + + if (iRecordType == ODocument.RECORD_TYPE) { + // No need lock, is already inside a lock. + OStorageOperationResult res = storage.readRecord(rid, null, false, false, null); + final ODocument storedRecord = new ODocument(rid).fromStream(res.getResult().getBuffer()); + final ODocument newRecord = new ODocument().fromStream(iRecordContent); + + final ODatabaseDocumentInternal currentDb = ODatabaseRecordThreadLocal.INSTANCE.get(); + hasSameContent = ODocumentHelper.hasSameContentOf(storedRecord, currentDb, newRecord, currentDb, null, false); + } else { + // CHECK BYTE PER BYTE + final ORecordAbstract storedRecord = rid.getRecord(); + hasSameContent = Arrays.equals(storedRecord.toStream(), iRecordContent); + } + + if (hasSameContent) + // OK + iDatabaseVersion.set(Math.max(iDatabaseVersion.get(), iRecordVersion)); + else + // NO DOCUMENT, CANNOT MERGE SO RELY TO THE VERSION CHECK + checkVersions(rid, iRecordVersion, iDatabaseVersion.get()); + + return null; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategy.java new file mode 100755 index 00000000000..5b009debd11 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategy.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.conflict; + +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Manages the MVCC conflicts. + * + * @author Luca Garulli + */ +public interface ORecordConflictStrategy { + byte[] onUpdate(OStorage storage, byte iRecordType, ORecordId rid, int iRecordVersion, byte[] iRecordContent, + AtomicInteger iDatabaseVersion); + + String getName(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategyFactory.java b/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategyFactory.java new file mode 100644 index 00000000000..1e40437ece6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/conflict/ORecordConflictStrategyFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.conflict; + +import com.orientechnologies.common.factory.OConfigurableStatelessFactory; + +/** + * Factory to manage the record conflict strategy implementations. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ORecordConflictStrategyFactory extends OConfigurableStatelessFactory { + public ORecordConflictStrategyFactory() { + final OVersionRecordConflictStrategy def = new OVersionRecordConflictStrategy(); + + registerImplementation(OVersionRecordConflictStrategy.NAME, def); + registerImplementation(OAutoMergeRecordConflictStrategy.NAME, new OAutoMergeRecordConflictStrategy()); + registerImplementation(OContentRecordConflictStrategy.NAME, new OContentRecordConflictStrategy()); + + setDefaultImplementation(def); + } + + public ORecordConflictStrategy getStrategy(final String iStrategy) { + return getImplementation(iStrategy); + } + + public String getDefaultStrategy() { + return OVersionRecordConflictStrategy.NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/conflict/OVersionRecordConflictStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/conflict/OVersionRecordConflictStrategy.java new file mode 100644 index 00000000000..9346a2f7ea4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/conflict/OVersionRecordConflictStrategy.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.conflict; + +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.exception.OFastConcurrentModificationException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Default strategy that checks the record version number: if the current update has a version different than stored one, then a + * OConcurrentModificationException is thrown. + * + * @author Luca Garulli + */ +public class OVersionRecordConflictStrategy implements ORecordConflictStrategy { + public static final String NAME = "version"; + + @Override + public byte[] onUpdate(OStorage storage, final byte iRecordType, final ORecordId rid, + final int iRecordVersion, final byte[] iRecordContent, final AtomicInteger iDatabaseVersion) { + checkVersions(rid, iRecordVersion, iDatabaseVersion.get()); + return null; + } + + @Override + public String getName() { + return NAME; + } + + protected void checkVersions(final ORecordId rid, final int iRecordVersion, final int iDatabaseVersion) { + if (OFastConcurrentModificationException.enabled()) + throw OFastConcurrentModificationException.instance(); + else + throw new OConcurrentModificationException(rid, iDatabaseVersion, iRecordVersion, ORecordOperation.UPDATED); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabase.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabase.java new file mode 100755 index 00000000000..96ab142ea69 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabase.java @@ -0,0 +1,914 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.cache.OLocalRecordCache; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategy; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OTransactionException; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.intent.OIntent; +import com.orientechnologies.orient.core.metadata.OMetadata; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.query.OQuery; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.ORecordCallback; +import com.orientechnologies.orient.core.storage.ORecordMetadata; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.tx.OTransaction; +import com.orientechnologies.orient.core.util.OBackupable; + +import java.io.Closeable; +import java.util.*; + +/** + * Generic Database interface. Represents the lower level of the Database providing raw API to access to the raw records.
      + * Limits: + *
        + *
      • Maximum records per cluster/class = 9.223.372.036 Billions: 2^63 = 9.223.372.036.854.775.808 records
      • + *
      • Maximum records per database = 302.231.454.903.657 Billions: 2^15 clusters x 2^63 records = (2^78) 32.768 * + * 9,223.372.036.854.775.808 = 302.231,454.903.657.293.676.544 records
      • + *
      • Maximum storage per database = 19.807.040.628.566.084 Terabytes: 2^31 data-segments x 2^63 bytes = (2^94) + * 2.147.483.648 x 9,223.372.036.854.775.808 Exabytes = 19.807,040.628.566.084.398.385.987.584 Yottabytes
      • + *
      + * + * @author Luca Garulli + */ +public interface ODatabase extends OBackupable, Closeable { + + enum OPTIONS { + SECURITY + } + + enum STATUS { + OPEN, CLOSED, IMPORTING + } + + enum ATTRIBUTES { + TYPE, STATUS, DEFAULTCLUSTERID, DATEFORMAT, DATETIMEFORMAT, TIMEZONE, LOCALECOUNTRY, LOCALELANGUAGE, CHARSET, CUSTOM, CLUSTERSELECTION, MINIMUMCLUSTERS, CONFLICTSTRATEGY, VALIDATION + } + + /** + * Opens a database using the user and password received as arguments. + * + * @param iUserName Username to login + * @param iUserPassword Password associated to the user + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB open(final String iUserName, final String iUserPassword); + + /** + * Creates a new database. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB create(); + + /** + * Creates new database from database backup. + * Only incremental backups are supported. + * + * @param incrementalBackupPath Path to incremental backup + * @param Concrete database instance type. + * + * @return he Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB create(String incrementalBackupPath); + + /** + * Creates a new database passing initial settings. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB create(Map iInitialSettings); + + /** + * Activate current database instance on current thread. Call this method before using the database if you switch between multiple + * databas instances on the same thread or if you pass them across threads. + */ + ODatabase activateOnCurrentThread(); + + /** + * Returns true if the current database instance is active on current thread, otherwise false. + */ + boolean isActiveOnCurrentThread(); + + /** + * Reloads the database information like the cluster list. + */ + void reload(); + + /** + * Drops a database. + * + * @throws ODatabaseException if database is closed. + */ + void drop(); + + /** + * Returns the database configuration settings. If defined, any database configuration overwrites the global one. + * + * @return OContextConfiguration + */ + OContextConfiguration getConfiguration(); + + /** + * Declares an intent to the database. Intents aim to optimize common use cases. + * + * @param iIntent The intent + */ + boolean declareIntent(final OIntent iIntent); + + /** + * Checks if the database exists. + * + * @return True if already exists, otherwise false. + */ + boolean exists(); + + /** + * Closes an opened database. + */ + void close(); + + /** + * Returns the current status of database. + */ + STATUS getStatus(); + + /** + * Returns the current status of database. + * deprecated since 2.2 + */ + @Deprecated + DB setStatus(STATUS iStatus); + + /** + * Returns the total size of database as the real used space. + */ + long getSize(); + + /** + * Returns the database name. + * + * @return Name of the database + */ + String getName(); + + /** + * Returns the database URL. + * + * @return URL of the database + */ + String getURL(); + + /** + * Returns the level1 cache. Cannot be null. + * + * @return Current cache. + */ + OLocalRecordCache getLocalCache(); + + /** + * Returns the default cluster id. If not specified all the new entities will be stored in the default cluster. + * + * @return The default cluster id + */ + int getDefaultClusterId(); + + /** + * Returns the number of clusters. + * + * @return Number of the clusters + */ + int getClusters(); + + /** + * Returns true if the cluster exists, otherwise false. + * + * @param iClusterName Cluster name + * + * @return true if the cluster exists, otherwise false + */ + boolean existsCluster(String iClusterName); + + /** + * Returns all the names of the clusters. + * + * @return Collection of cluster names. + */ + Collection getClusterNames(); + + /** + * Returns the cluster id by name. + * + * @param iClusterName Cluster name + * + * @return The id of searched cluster. + */ + int getClusterIdByName(String iClusterName); + + /** + * Returns the cluster name by id. + * + * @param iClusterId Cluster id + * + * @return The name of searched cluster. + */ + String getClusterNameById(int iClusterId); + + /** + * Returns the total size of records contained in the cluster defined by its name. + * + * @param iClusterName Cluster name + * + * @return Total size of records contained. + */ + long getClusterRecordSizeByName(String iClusterName); + + /** + * Returns the total size of records contained in the cluster defined by its id. + * + * @param iClusterId Cluster id + * + * @return The name of searched cluster. + */ + long getClusterRecordSizeById(int iClusterId); + + /** + * Checks if the database is closed. + * + * @return true if is closed, otherwise false. + */ + boolean isClosed(); + + /** + * Removes all data in the cluster with given name. + * As result indexes for this class will be rebuilt. + * + * @param clusterName Name of cluster to be truncated. + */ + void truncateCluster(String clusterName); + + /** + * Counts all the entities in the specified cluster id. + * + * @param iCurrentClusterId Cluster id + * + * @return Total number of entities contained in the specified cluster + */ + long countClusterElements(int iCurrentClusterId); + + long countClusterElements(int iCurrentClusterId, boolean countTombstones); + + /** + * Counts all the entities in the specified cluster ids. + * + * @param iClusterIds Array of cluster ids Cluster id + * + * @return Total number of entities contained in the specified clusters + */ + long countClusterElements(int[] iClusterIds); + + long countClusterElements(int[] iClusterIds, boolean countTombstones); + + /** + * Counts all the entities in the specified cluster name. + * + * @param iClusterName Cluster name + * + * @return Total number of entities contained in the specified cluster + */ + long countClusterElements(String iClusterName); + + /** + * Adds a new cluster. + * + * @param iClusterName Cluster name + * @param iParameters Additional parameters to pass to the factories + * + * @return Cluster id + */ + int addCluster(String iClusterName, Object... iParameters); + + /** + * Adds a new cluster for store blobs. + * + * @param iClusterName Cluster name + * @param iParameters Additional parameters to pass to the factories + * + * @return Cluster id + */ + int addBlobCluster(String iClusterName, Object... iParameters); + + /** + * Alters a cluster. + * + * @param iClusterName The name of the cluster to alter + * @param attribute The cluster attribute to be modified + * + * @return Dependent on attribute type + */ + Object alterCluster(String iClusterName, OCluster.ATTRIBUTES attribute, Object value); + + /** + * Alters a cluster. + * + * @param iClusterId The id of the cluster to alter + * @param attribute The cluster attribute to be modified + * + * @return Dependent on attribute type + */ + Object alterCluster(int iClusterId, OCluster.ATTRIBUTES attribute, Object value); + + /** + * Retrieve the set of defined blob cluster. + * + * @return the set of defined blob cluster ids. + */ + Set getBlobClusterIds(); + + /** + * Adds a new cluster. + * + * @param iClusterName Cluster name + * @param iRequestedId requested id of the cluster + * @param iParameters Additional parameters to pass to the factories + * + * @return Cluster id + */ + int addCluster(String iClusterName, int iRequestedId, Object... iParameters); + + /** + * Drops a cluster by its name. Physical clusters will be completely deleted + * + * @param iClusterName the name of the cluster + * + * @return true if has been removed, otherwise false + */ + boolean dropCluster(String iClusterName, final boolean iTruncate); + + /** + * Drops a cluster by its id. Physical clusters will be completely deleted. + * + * @param iClusterId id of cluster to delete + * + * @return true if has been removed, otherwise false + */ + boolean dropCluster(int iClusterId, final boolean iTruncate); + + /** + * Sets a property value + * + * @param iName Property name + * @param iValue new value to set + * + * @return The previous value if any, otherwise null + */ + Object setProperty(String iName, Object iValue); + + /** + * Gets the property value. + * + * @param iName Property name + * + * @return The previous value if any, otherwise null + */ + Object getProperty(String iName); + + /** + * Returns an iterator of the property entries + */ + Iterator> getProperties(); + + /** + * Returns a database attribute value + * + * @param iAttribute Attributes between #ATTRIBUTES enum + * + * @return The attribute value + */ + Object get(ATTRIBUTES iAttribute); + + /** + * Sets a database attribute value + * + * @param iAttribute Attributes between #ATTRIBUTES enum + * @param iValue Value to set + * + * @return underlying + */ + DB set(ATTRIBUTES iAttribute, Object iValue); + + /** + * Registers a listener to the database events. + * + * @param iListener the listener to register + */ + void registerListener(ODatabaseListener iListener); + + /** + * Unregisters a listener to the database events. + * + * @param iListener the listener to unregister + */ + void unregisterListener(ODatabaseListener iListener); + + @Deprecated + ORecordMetadata getRecordMetadata(final ORID rid); + + /** + * Flush cached storage content to the disk. + *

      + * After this call users can perform only idempotent calls like read records and select/traverse queries. All write-related + * operations will queued till {@link #release()} command will be called. + *

      + * Given command waits till all on going modifications in indexes or DB will be finished. + *

      + * IMPORTANT: This command is not reentrant. + * + * @see #release() + */ + void freeze(); + + /** + * Returns true if the database is frozen ({@link #freeze()} operation), otherwise false. + */ + boolean isFrozen(); + + /** + * Allows to execute write-related commands on DB. Called after {@link #freeze()} command. + * + * @see #freeze() + */ + void release(); + + /** + * Flush cached storage content to the disk. + *

      + * After this call users can perform only select queries. All write-related commands will queued till {@link #release()} command + * will be called or exception will be thrown on attempt to modify DB data. Concrete behaviour depends on + * throwException parameter. + *

      + * IMPORTANT: This command is not reentrant. + * + * @param throwException If true {@link com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException} + * exception will be thrown in case of write command will be performed. + */ + void freeze(boolean throwException); + + enum OPERATION_MODE { + SYNCHRONOUS, ASYNCHRONOUS, ASYNCHRONOUS_NOANSWER + } + + /** + * Creates a new entity instance. + * + * @return The new instance. + */ + RET newInstance(); + + /** + * Returns the Dictionary manual index. + * + * @return ODictionary instance + */ + ODictionary getDictionary(); + + /** + * Returns the current user logged into the database. + * + * @see com.orientechnologies.orient.core.metadata.security.OSecurity + */ + OSecurityUser getUser(); + + /** + * Loads the entity and return it. + * + * @param iObject The entity to load. If the entity was already loaded it will be reloaded and all the changes will be lost. + * + * @return + */ + RET load(T iObject); + + /** + * Loads a record using a fetch plan. + * + * @param iObject Record to load + * @param iFetchPlan Fetch plan used + * + * @return The record received + */ + RET load(T iObject, String iFetchPlan); + + /** + * Loads a record using a fetch plan. + * + * @param iObject Record to load + * @param iFetchPlan Fetch plan used + * @param iLockingStrategy + * + * @return The record received + * + * @deprecated Usage of this method may lead to deadlocks. + */ + @Deprecated + RET load(T iObject, String iFetchPlan, boolean iIgnoreCache, boolean loadTombstone, + OStorage.LOCKING_STRATEGY iLockingStrategy); + + /** + * Loads a record using a fetch plan. + * + * @param iObject Record to load + * @param iFetchPlan Fetch plan used + * @param iLockingStrategy + * + * @return The record received + * + * @deprecated Usage of this method may lead to deadlocks. + */ + @Deprecated + RET load(T iObject, String iFetchPlan, boolean iIgnoreCache, boolean iUpdateCache, boolean loadTombstone, + OStorage.LOCKING_STRATEGY iLockingStrategy); + + /** + * Loads a record using a fetch plan. + * + * @param iObject Record to load + * @param iFetchPlan Fetch plan used + * @param iIgnoreCache Ignore cache or use it + * + * @return The record received + */ + RET load(T iObject, String iFetchPlan, boolean iIgnoreCache); + + /** + * Force the reloading of the entity. + * + * @param iObject The entity to load. If the entity was already loaded it will be reloaded and all the changes will be lost. + * @param iFetchPlan Fetch plan used + * @param iIgnoreCache Ignore cache or use it + * + * @return The loaded entity + */ + RET reload(final T iObject, String iFetchPlan, boolean iIgnoreCache); + + /** + * Force the reloading of the entity. + * + * @param iObject The entity to load. If the entity was already loaded it will be reloaded and all the changes will be lost. + * @param iFetchPlan Fetch plan used + * @param iIgnoreCache Ignore cache or use it + * @param force Force to reload record even if storage has the same record as reloaded record, it is useful if fetch plan + * is not null and alongside with root record linked records will be reloaded. + * + * @return The loaded entity + */ + RET reload(final T iObject, String iFetchPlan, boolean iIgnoreCache, boolean force); + + /** + * Loads the entity by the Record ID. + * + * @param recordId The unique record id of the entity to load. + * + * @return The loaded entity + */ + RET load(ORID recordId); + + /** + * Loads the entity by the Record ID using a fetch plan. + * + * @param iRecordId The unique record id of the entity to load. + * @param iFetchPlan Fetch plan used + * + * @return The loaded entity + */ + RET load(ORID iRecordId, String iFetchPlan); + + /** + * Loads the entity by the Record ID using a fetch plan and specifying if the cache must be ignored. + * + * @param iRecordId The unique record id of the entity to load. + * @param iFetchPlan Fetch plan used + * @param iIgnoreCache Ignore cache or use it + * + * @return The loaded entity + */ + RET load(ORID iRecordId, String iFetchPlan, boolean iIgnoreCache); + + @Deprecated + /** + * @deprecated Usage of this method may lead to deadlocks. + */ + RET load(ORID iRecordId, String iFetchPlan, boolean iIgnoreCache, boolean loadTombstone, + OStorage.LOCKING_STRATEGY iLockingStrategy); + + @Deprecated + /** + * @deprecated Usage of this method may lead to deadlocks. + */ + RET load(ORID iRecordId, String iFetchPlan, boolean iIgnoreCache, boolean iUpdateCache, boolean loadTombstone, + OStorage.LOCKING_STRATEGY iLockingStrategy); + + /** + * Saves an entity in synchronous mode. If the entity is not dirty, then the operation will be ignored. For custom entity + * implementations assure to set the entity as dirty. + * + * @param iObject The entity to save + * + * @return The saved entity. + */ + RET save(T iObject); + + /** + * Saves an entity specifying the mode. If the entity is not dirty, then the operation will be ignored. For custom entity + * implementations assure to set the entity as dirty. If the cluster does not exist, an error will be thrown. + * + * @param iObject The entity to save + * @param iMode Mode of save: synchronous (default) or asynchronous + * @param iForceCreate Flag that indicates that record should be created. If record with current rid already exists, + * exception is thrown + * @param iRecordCreatedCallback + * @param iRecordUpdatedCallback + */ + RET save(T iObject, OPERATION_MODE iMode, boolean iForceCreate, + ORecordCallback iRecordCreatedCallback, ORecordCallback iRecordUpdatedCallback); + + /** + * Saves an entity in the specified cluster in synchronous mode. If the entity is not dirty, then the operation will be ignored. + * For custom entity implementations assure to set the entity as dirty. If the cluster does not exist, an error will be thrown. + * + * @param iObject The entity to save + * @param iClusterName Name of the cluster where to save + * + * @return The saved entity. + */ + RET save(T iObject, String iClusterName); + + /** + * Saves an entity in the specified cluster specifying the mode. If the entity is not dirty, then the operation will be ignored. + * For custom entity implementations assure to set the entity as dirty. If the cluster does not exist, an error will be thrown. + * + * @param iObject The entity to save + * @param iClusterName Name of the cluster where to save + * @param iMode Mode of save: synchronous (default) or asynchronous + * @param iForceCreate Flag that indicates that record should be created. If record with current rid already exists, + * exception is thrown + * @param iRecordCreatedCallback + * @param iRecordUpdatedCallback + */ + RET save(T iObject, String iClusterName, OPERATION_MODE iMode, boolean iForceCreate, + ORecordCallback iRecordCreatedCallback, ORecordCallback iRecordUpdatedCallback); + + /** + * Deletes an entity from the database in synchronous mode. + * + * @param iObject The entity to delete. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + ODatabase delete(T iObject); + + /** + * Deletes the entity with the received RID from the database. + * + * @param iRID The RecordID to delete. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + ODatabase delete(ORID iRID); + + /** + * Deletes the entity with the received RID from the database. + * + * @param iRID The RecordID to delete. + * @param iVersion for MVCC + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + ODatabase delete(ORID iRID, int iVersion); + + /** + * Hides records content by putting tombstone on the records position but does not delete record itself. + *

      + * This method is used in case of record content itself is broken and cannot be read or deleted. So it is emergence method. This + * method can be used only if there is no active transaction in database. + * + * @param rid record id. + * + * @return true if record was hidden and false if record does not exits in database. + * + * @throws java.lang.UnsupportedOperationException In case current version of cluster does not + * support given operation. + * @throws com.orientechnologies.orient.core.exception.ORecordNotFoundException if record is already deleted/hidden. + */ + + boolean hide(ORID rid); + + ODatabase cleanOutRecord(ORID rid, int version); + + /** + * Return active transaction. Cannot be null. If no transaction is active, then a OTransactionNoTx instance is returned. + * + * @return OTransaction implementation + */ + OTransaction getTransaction(); + + /** + * Begins a new transaction. By default the type is OPTIMISTIC. If a previous transaction was started it will be rollbacked and + * closed before to start a new one. A transaction once begun has to be closed by calling the {@link #commit()} or + * {@link #rollback()}. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + ODatabase begin(); + + /** + * Begins a new transaction specifying the transaction type. If a previous transaction was started it will be rollbacked and + * closed before to start a new one. A transaction once begun has to be closed by calling the {@link #commit()} or + * {@link #rollback()}. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + ODatabase begin(OTransaction.TXTYPE iStatus); + + /** + * Attaches a transaction as current. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + @Deprecated + ODatabase begin(OTransaction iTx) throws OTransactionException; + + /** + * Commits the current transaction. The approach is all or nothing. All changes will be permanent following the storage type. If + * the operation succeed all the entities changed inside the transaction context will be effectives. If the operation fails, all + * the changed entities will be restored in the datastore. Memory instances are not guaranteed to being restored as well. + * + * @return + */ + ODatabase commit() throws OTransactionException; + + ODatabase commit(boolean force) throws OTransactionException; + + /** + * Aborts the current running transaction. All the pending changed entities will be restored in the datastore. Memory instances + * are not guaranteed to being restored as well. + * + * @return + */ + ODatabase rollback() throws OTransactionException; + + ODatabase rollback(boolean force) throws OTransactionException; + + /** + * Execute a query against the database. If the OStorage used is remote (OStorageRemote) then the command will be executed + * remotely and the result returned back to the calling client. + * + * @param iCommand Query command + * @param iArgs Optional parameters to bind to the query + * + * @return List of POJOs + */ + > RET query(final OQuery iCommand, final Object... iArgs); + + /** + * Execute a command against the database. A command can be a SQL statement or a Procedure. If the OStorage used is remote + * (OStorageRemote) then the command will be executed remotely and the result returned back to the calling client. + * + * @param iCommand Command request to execute. + * + * @return The same Command request received as parameter. + */ + RET command(OCommandRequest iCommand); + + /** + * Return the OMetadata instance. Cannot be null. + * + * @return The OMetadata instance. + */ + OMetadata getMetadata(); + + /** + * Registers a hook to listen all events for Records. + * + * @param iHookImpl ORecordHook implementation + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + > DB registerHook(ORecordHook iHookImpl); + + > DB registerHook(final ORecordHook iHookImpl, ORecordHook.HOOK_POSITION iPosition); + + /** + * Retrieves all the registered hooks. + * + * @return A not-null unmodifiable map of ORecordHook and position instances. If there are no hooks registered, the Map is empty. + */ + Map getHooks(); + + /** + * Unregisters a previously registered hook. + * + * @param iHookImpl ORecordHook implementation + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. deprecated since + * 2.2 + */ + > DB unregisterHook(ORecordHook iHookImpl); + + /** + * Returns if the Multi Version Concurrency Control is enabled or not. If enabled the version of the record is checked before each + * update and delete against the records. + * + * @return true if enabled, otherwise false + * + * @see com.orientechnologies.orient.core.db.document.ODatabaseDocument#setMVCC(boolean) deprecated since 2.2 + */ + @Deprecated + boolean isMVCC(); + + /** + * Retrieves all the registered listeners. + * + * @return An iterable of ODatabaseListener instances. + */ + Iterable getListeners(); + + /** + * Enables or disables the Multi-Version Concurrency Control. If enabled the version of the record is checked before each update + * and delete against the records. + * + * @param iValue + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. deprecated since + * 2.2 + * + * @see com.orientechnologies.orient.core.db.document.ODatabaseDocument#isMVCC() + */ + @Deprecated + > DB setMVCC(boolean iValue); + + String getType(); + + /** + * Returns the current record conflict strategy. + */ + ORecordConflictStrategy getConflictStrategy(); + + /** + * Overrides record conflict strategy selecting the strategy by name. + * + * @param iStrategyName ORecordConflictStrategy strategy name + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + > DB setConflictStrategy(String iStrategyName); + + /** + * Overrides record conflict strategy. + * + * @param iResolver ORecordConflictStrategy implementation + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + > DB setConflictStrategy(ORecordConflictStrategy iResolver); + + /** + * Performs incremental backup of database content to the selected folder. This is thread safe operation and can be done in normal + * operational mode. + *

      + * If it will be first backup of data full content of database will be copied into folder otherwise only changes after last backup + * in the same folder will be copied. + * + * @param path Path to backup folder. + * + * @return File name of the backup + * + * @since 2.2 + */ + String incrementalBackup(String path); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseDocumentInternal.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseDocumentInternal.java new file mode 100644 index 00000000000..65efdce5342 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseDocumentInternal.java @@ -0,0 +1,78 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OCurrentStorageComponentsFactory; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeCollectionManager; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; + +public interface ODatabaseDocumentInternal extends ODatabaseDocument, ODatabaseInternal { + + /** + * Internal. Returns the factory that defines a set of components that current database should use to be compatible to current + * version of storage. So if you open a database create with old version of OrientDB it defines a components that should be used + * to provide backward compatibility with that version of database. + */ + OCurrentStorageComponentsFactory getStorageVersions(); + + /** + * Internal. Gets an instance of sb-tree collection manager for current database. + */ + OSBTreeCollectionManager getSbTreeCollectionManager(); + + /** + * @return the factory of binary serializers. + */ + OBinarySerializerFactory getSerializerFactory(); + + /** + * @return serializer which is used for document serialization. + */ + ORecordSerializer getSerializer(); + + int assignAndCheckCluster(ORecord record, String iClusterName); + + RET loadIfVersionIsNotLatest(ORID rid, int recordVersion, String fetchPlan, boolean ignoreCache) + throws ORecordNotFoundException; + + void reloadUser(); + + ORecordHook.RESULT callbackHooks(ORecordHook.TYPE type, OIdentifiable id); + + @Override + OMetadataInternal getMetadata(); + + boolean isPrefetchRecords(); + + void setPrefetchRecords(boolean prefetchRecords); + + ODatabaseDocumentInternal copy(); + + void recycle(ORecord record); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseInternal.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseInternal.java new file mode 100644 index 00000000000..765630f18ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseInternal.java @@ -0,0 +1,88 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.metadata.security.OToken; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.concurrent.Callable; + +public interface ODatabaseInternal extends ODatabase { + + /** + * Returns the underlying storage implementation. + * + * @return The underlying storage implementation + * @see OStorage + */ + OStorage getStorage(); + + /** + * Set user for current database instance. + */ + void setUser(OSecurityUser user); + + /** + * Internal only: replace the storage with a new one. + * + * @param iNewStorage + * The new storage to use. Usually it's a wrapped instance of the current cluster. + */ + void replaceStorage(OStorage iNewStorage); + + V callInLock(Callable iCallable, boolean iExclusiveLock); + + void resetInitialization(); + + /** + * Returns the database owner. Used in wrapped instances to know the up level ODatabase instance. + * + * @return Returns the database owner. + */ + ODatabaseInternal getDatabaseOwner(); + + /** + * Internal. Sets the database owner. + */ + ODatabaseInternal setDatabaseOwner(ODatabaseInternal iOwner); + + /** + * Return the underlying database. Used in wrapper instances to know the down level ODatabase instance. + * + * @return The underlying ODatabase implementation. + */ + DB getUnderlying(); + + /** + * Internal method. Don't call it directly unless you're building an internal component. + */ + void setInternal(ATTRIBUTES attribute, Object iValue); + + /** + * Opens a database using an authentication token received as an argument. + * + * @param iToken + * Authentication token + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB open(final OToken iToken); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListener.java new file mode 100644 index 00000000000..481ed83ea2a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListener.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Listener Interface to receive callbacks on database usage. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ODatabaseLifecycleListener { + enum PRIORITY { + FIRST, EARLY, REGULAR, LATE, LAST + } + + PRIORITY getPriority(); + + void onCreate(ODatabaseInternal iDatabase); + + void onOpen(ODatabaseInternal iDatabase); + + void onClose(ODatabaseInternal iDatabase); + + void onDrop(ODatabaseInternal iDatabase); + + void onCreateClass(ODatabaseInternal iDatabase, OClass iClass); + + void onDropClass(ODatabaseInternal iDatabase, OClass iClass); + + /** + * Event called during the retrieving of distributed configuration, usually at startup and when the cluster shape changes. You can + * use this event to enrich the ODocument sent to the client with custom properties. + * + * @param iConfiguration + */ + void onLocalNodeConfigurationRequest(ODocument iConfiguration); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListenerAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListenerAbstract.java new file mode 100644 index 00000000000..0b584d7a718 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseLifecycleListenerAbstract.java @@ -0,0 +1,71 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Abstract Listener Interface to receive callbacks on database usage. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + */ +public abstract class ODatabaseLifecycleListenerAbstract implements ODatabaseLifecycleListener { + + @Override + public PRIORITY getPriority() { + return PRIORITY.REGULAR; + } + + @Override + public void onCreate(ODatabaseInternal iDatabase) { + + } + + @Override + public void onOpen(ODatabaseInternal iDatabase) { + + } + + @Override + public void onClose(ODatabaseInternal iDatabase) { + + } + + @Override + public void onDrop(ODatabaseInternal iDatabase) { + + } + + @Override + public void onCreateClass(ODatabaseInternal iDatabase, OClass iClass) { + + } + + @Override + public void onDropClass(ODatabaseInternal iDatabase, OClass iClass) { + + } + + @Override + public void onLocalNodeConfigurationRequest(ODocument iConfiguration) { + + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseListener.java new file mode 100644 index 00000000000..3759a9b57ba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseListener.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.command.OCommandRequestText; + +/** + * Listener Interface for all the events of the Database instances. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ODatabaseListener { + + void onCreate(final ODatabase iDatabase); + + void onDelete(final ODatabase iDatabase); + + void onOpen(final ODatabase iDatabase); + + void onBeforeTxBegin(final ODatabase iDatabase); + + void onBeforeTxRollback(final ODatabase iDatabase); + + void onAfterTxRollback(final ODatabase iDatabase); + + void onBeforeTxCommit(final ODatabase iDatabase); + + void onAfterTxCommit(final ODatabase iDatabase); + + void onClose(final ODatabase iDatabase); + + void onBeforeCommand(final OCommandRequestText iCommand, final OCommandExecutor executor); + + void onAfterCommand(final OCommandRequestText iCommand, final OCommandExecutor executor, Object result); + + /** + * Callback to decide if repair the database upon corruption. + * + * @param iDatabase + * Target database + * @param iReason + * Reason of corruption + * @param iWhatWillbeFixed + * TODO + * @return true if repair must be done, otherwise false + */ + boolean onCorruptionRepairDatabase(final ODatabase iDatabase, final String iReason, String iWhatWillbeFixed); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolAbstract.java new file mode 100644 index 00000000000..52548725bdb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolAbstract.java @@ -0,0 +1,361 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import com.orientechnologies.common.concur.lock.OAdaptiveLock; +import com.orientechnologies.common.concur.lock.OLockException; +import com.orientechnologies.common.concur.resource.OReentrantResourcePool; +import com.orientechnologies.common.concur.resource.OResourcePoolListener; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.storage.OStorage; + +public abstract class ODatabasePoolAbstract extends OAdaptiveLock implements + OResourcePoolListener, OOrientListener { + + private final HashMap> pools = new HashMap>(); + protected Object owner; + private int maxSize; + private int timeout; + private volatile Timer evictionTask; + private Evictor evictor; + + /** + * The idle object evictor {@link TimerTask}. + */ + class Evictor extends TimerTask { + + private HashMap> evictionMap = new HashMap>(); + private long minIdleTime; + + public Evictor(long minIdleTime) { + this.minIdleTime = minIdleTime; + } + + /** + * Run pool maintenance. Evict objects qualifying for eviction + */ + @Override + public void run() { + OLogManager.instance().debug(this, "Running Connection Pool Evictor Service..."); + lock(); + try { + for (Entry> pool : this.evictionMap.entrySet()) { + Map poolDbs = pool.getValue(); + Iterator> iterator = poolDbs.entrySet().iterator(); + while (iterator.hasNext()) { + Entry db = iterator.next(); + if (System.currentTimeMillis() - db.getValue() >= this.minIdleTime) { + + OReentrantResourcePool oResourcePool = pools.get(pool.getKey()); + if (oResourcePool != null) { + OLogManager.instance().debug(this, "Closing idle pooled database '%s'...", db.getKey().getName()); + ((ODatabasePooled) db.getKey()).forceClose(); + oResourcePool.remove(db.getKey()); + iterator.remove(); + } + + } + } + + } + } finally { + unlock(); + } + } + + public void updateIdleTime(final String poolName, final DB iDatabase) { + Map pool = this.evictionMap.get(poolName); + if (pool == null) { + pool = new HashMap(); + this.evictionMap.put(poolName, pool); + } + + pool.put(iDatabase, System.currentTimeMillis()); + + } + } + + public ODatabasePoolAbstract(final Object iOwner, final int iMinSize, final int iMaxSize) { + this(iOwner, iMinSize, iMaxSize, OGlobalConfiguration.CLIENT_CONNECT_POOL_WAIT_TIMEOUT.getValueAsInteger(), + OGlobalConfiguration.DB_POOL_IDLE_TIMEOUT.getValueAsLong(), OGlobalConfiguration.DB_POOL_IDLE_CHECK_DELAY.getValueAsLong()); + } + + public ODatabasePoolAbstract(final Object iOwner, final int iMinSize, final int iMaxSize, final long idleTimeout, + final long timeBetweenEvictionRunsMillis) { + this(iOwner, iMinSize, iMaxSize, OGlobalConfiguration.CLIENT_CONNECT_POOL_WAIT_TIMEOUT.getValueAsInteger(), idleTimeout, + timeBetweenEvictionRunsMillis); + } + + public ODatabasePoolAbstract(final Object iOwner, final int iMinSize, final int iMaxSize, final int iTimeout, + final long idleTimeoutMillis, final long timeBetweenEvictionRunsMillis) { + super(OGlobalConfiguration.ENVIRONMENT_CONCURRENT.getValueAsBoolean(), OGlobalConfiguration.STORAGE_LOCK_TIMEOUT + .getValueAsInteger(), true); + + maxSize = iMaxSize; + timeout = iTimeout; + owner = iOwner; + Orient.instance().registerListener(this); + + if (idleTimeoutMillis > 0 && timeBetweenEvictionRunsMillis > 0) { + this.evictionTask = new Timer(); + this.evictor = new Evictor(idleTimeoutMillis); + this.evictionTask.schedule(evictor, timeBetweenEvictionRunsMillis, timeBetweenEvictionRunsMillis); + } + } + + public DB acquire(final String iURL, final String iUserName, final String iUserPassword) throws OLockException { + return acquire(iURL, iUserName, iUserPassword, null); + } + + public DB acquire(final String iURL, final String iUserName, final String iUserPassword, final Map iOptionalParams) + throws OLockException { + final String dbPooledName = OIOUtils.getUnixFileName(iUserName + "@" + iURL); + OReentrantResourcePool pool; + lock(); + try { + pool = pools.get(dbPooledName); + if (pool == null) + // CREATE A NEW ONE + pool = new OReentrantResourcePool(maxSize, this); + + // PUT IN THE POOL MAP ONLY IF AUTHENTICATION SUCCEED + pools.put(dbPooledName, pool); + + } finally { + unlock(); + } + final DB db = pool.getResource(iURL, timeout, iUserName, iUserPassword, iOptionalParams); + return db; + } + + public int getMaxConnections(final String url, final String userName) { + final String dbPooledName = OIOUtils.getUnixFileName(userName + "@" + url); + final OReentrantResourcePool pool; + lock(); + try { + pool = pools.get(dbPooledName); + } finally { + unlock(); + } + if (pool == null) + return maxSize; + + return pool.getMaxResources(); + } + + public int getCreatedInstances(String url, String userName) { + final String dbPooledName = OIOUtils.getUnixFileName(userName + "@" + url); + lock(); + try { + final OReentrantResourcePool pool = pools.get(dbPooledName); + if (pool == null) + return 0; + + return pool.getCreatedInstances(); + } finally { + unlock(); + } + } + + public int getAvailableConnections(final String url, final String userName) { + final String dbPooledName = OIOUtils.getUnixFileName(userName + "@" + url); + final OReentrantResourcePool pool; + lock(); + try { + pool = pools.get(dbPooledName); + } finally { + unlock(); + } + if (pool == null) + return 0; + + return pool.getAvailableResources(); + } + + public int getConnectionsInCurrentThread(final String url, final String userName) { + final String dbPooledName = OIOUtils.getUnixFileName(userName + "@" + url); + final OReentrantResourcePool pool; + lock(); + try { + pool = pools.get(dbPooledName); + } finally { + unlock(); + } + if (pool == null) + return 0; + + return pool.getConnectionsInCurrentThread(url); + } + + public void release(final DB iDatabase) { + // REMOVE ANY INTENT BEFORE. THIS RESTORE ANYTHING BEFORE THE CLOSE, LIKE THE USER NAME IN CASE OF MASSIVE INSERT + iDatabase.declareIntent(null); + + final String dbPooledName = iDatabase.getUser().getName() + "@" + iDatabase.getURL(); + final OReentrantResourcePool pool; + lock(); + try { + + pool = pools.get(dbPooledName); + + } finally { + unlock(); + } + if (pool == null) + throw new OLockException("Cannot release a database URL not acquired before. URL: " + iDatabase.getName()); + + if (pool.returnResource(iDatabase)) + this.notifyEvictor(dbPooledName, iDatabase); + } + + public Map> getPools() { + lock(); + try { + + return Collections.unmodifiableMap(pools); + + } finally { + unlock(); + } + } + + /** + * Closes all the databases. + */ + public void close() { + lock(); + try { + + if (this.evictionTask != null) { + this.evictionTask.cancel(); + } + + for (Entry> pool : pools.entrySet()) { + for (DB db : pool.getValue().getResources()) { + pool.getValue().close(); + try { + OLogManager.instance().debug(this, "Closing pooled database '%s'...", db.getName()); + ((ODatabasePooled) db).forceClose(); + OLogManager.instance().debug(this, "OK", db.getName()); + } catch (Exception e) { + OLogManager.instance().debug(this, "Error: %d", e.toString()); + } + } + } + + } finally { + unlock(); + } + } + + public void remove(final String iName, final String iUser) { + remove(iUser + "@" + iName); + } + + public void remove(final String iPoolName) { + lock(); + try { + + final OReentrantResourcePool pool = pools.remove(iPoolName); + + if (pool != null) { + for (DB db : pool.getResources()) { + final OStorage stg = db.getStorage(); + if (stg != null && stg.getStatus() == OStorage.STATUS.OPEN) + try { + OLogManager.instance().debug(this, "Closing pooled database '%s'...", db.getName()); + db.activateOnCurrentThread(); + ((ODatabasePooled) db).forceClose(); + OLogManager.instance().debug(this, "OK", db.getName()); + } catch (Exception e) { + OLogManager.instance().debug(this, "Error: %d", e.toString()); + } + + } + pool.close(); + } + + } finally { + unlock(); + } + } + + public int getMaxSize() { + return maxSize; + } + + public void onStorageRegistered(final OStorage iStorage) { + } + + /** + * Removes from memory the pool associated to the closed storage. This avoids pool open against closed storages. + */ + public void onStorageUnregistered(final OStorage iStorage) { + final String storageURL = iStorage.getURL(); + + lock(); + try { + Set poolToClose = null; + + for (Entry> e : pools.entrySet()) { + final int pos = e.getKey().indexOf("@"); + final String dbName = e.getKey().substring(pos + 1); + if (storageURL.equals(dbName)) { + if (poolToClose == null) + poolToClose = new HashSet(); + + poolToClose.add(e.getKey()); + } + } + + if (poolToClose != null) + for (String pool : poolToClose) + remove(pool); + + } finally { + unlock(); + } + } + + @Override + public void onShutdown() { + close(); + } + + private void notifyEvictor(final String poolName, final DB iDatabase) { + if (this.evictor != null) { + this.evictor.updateIdleTime(poolName, iDatabase); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolBase.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolBase.java new file mode 100644 index 00000000000..7af530374d3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePoolBase.java @@ -0,0 +1,225 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.common.concur.resource.OReentrantResourcePool; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; + +import java.util.Map; + +/** + * Database pool base class. + * + * @author Luca Garulli + * + */ +public abstract class ODatabasePoolBase extends Thread { + protected final String url; + protected final String userName; + protected final String userPassword; + protected ODatabasePoolAbstract dbPool; + + protected ODatabasePoolBase() { + url = userName = userPassword = null; + } + + protected ODatabasePoolBase(final String iURL, final String iUserName, final String iUserPassword) { + url = iURL; + userName = iUserName; + userPassword = iUserPassword; + } + + public ODatabasePoolBase setup() { + if (dbPool == null) + setup(OGlobalConfiguration.DB_POOL_MIN.getValueAsInteger(), OGlobalConfiguration.DB_POOL_MAX.getValueAsInteger()); + + return this; + } + + public ODatabasePoolBase setup(final int iMinSize, final int iMaxSize) { + if (dbPool == null) + setup(iMinSize, iMaxSize, OGlobalConfiguration.DB_POOL_IDLE_TIMEOUT.getValueAsLong(), + OGlobalConfiguration.DB_POOL_IDLE_CHECK_DELAY.getValueAsLong()); + + return this; + } + + public ODatabasePoolBase setup(final int iMinSize, final int iMaxSize, final long idleTimeout, + final long timeBetweenEvictionRunsMillis) { + if (dbPool == null) + synchronized (this) { + if (dbPool == null) { + dbPool = new ODatabasePoolAbstract(this, iMinSize, iMaxSize, idleTimeout, timeBetweenEvictionRunsMillis) { + + public void onShutdown() { + if (owner instanceof ODatabasePoolBase) + ((ODatabasePoolBase) owner).close(); + } + + public DB createNewResource(final String iDatabaseName, final Object... iAdditionalArgs) { + if (iAdditionalArgs.length < 2) + throw new OSecurityAccessException("Username and/or password missed"); + + return createResource(owner, iDatabaseName, iAdditionalArgs); + } + + public boolean reuseResource(final String iKey, final Object[] iAdditionalArgs, final DB iValue) { + if (((ODatabasePooled) iValue).isUnderlyingOpen()) { + ((ODatabasePooled) iValue).reuse(owner, iAdditionalArgs); + if (iValue.getStorage().isClosed()) + // STORAGE HAS BEEN CLOSED: REOPEN IT + iValue.getStorage().open((String) iAdditionalArgs[0], (String) iAdditionalArgs[1], null); + else if (!iValue.getUser().checkPassword((String) iAdditionalArgs[1])) + throw new OSecurityAccessException(iValue.getName(), "User or password not valid for database: '" + + iValue.getName() + "'"); + + return true; + } + return false; + } + }; + } + } + return this; + } + + /** + * Acquires a connection from the pool using the configured URL, user-name and user-password. If the pool is empty, then the + * caller thread will wait for it. + * + * @return A pooled database instance + */ + public DB acquire() { + setup(); + return dbPool.acquire(url, userName, userPassword); + } + + /** + * Acquires a connection from the pool. If the pool is empty, then the caller thread will wait for it. + * + * @param iName + * Database name + * @param iUserName + * User name + * @param iUserPassword + * User password + * @return A pooled database instance + */ + public DB acquire(final String iName, final String iUserName, final String iUserPassword) { + setup(); + return dbPool.acquire(iName, iUserName, iUserPassword); + } + + /** + * Returns amount of available connections which you can acquire for given source and user name. Source id is consist of + * "source name" and "source user name". + * + * @param name + * Source name. + * @param userName + * User name which is used to acquire source. + * @return amount of available connections which you can acquire for given source and user name. + */ + public int getAvailableConnections(final String name, final String userName) { + setup(); + return dbPool.getAvailableConnections(name, userName); + } + + public int getCreatedInstances(final String name, final String userName) { + setup(); + return dbPool.getCreatedInstances(name, userName); + } + + /** + * Acquires a connection from the pool specifying options. If the pool is empty, then the caller thread will wait for it. + * + * @param iName + * Database name + * @param iUserName + * User name + * @param iUserPassword + * User password + * @return A pooled database instance + */ + public DB acquire(final String iName, final String iUserName, final String iUserPassword, + final Map iOptionalParams) { + setup(); + return dbPool.acquire(iName, iUserName, iUserPassword, iOptionalParams); + } + + public int getConnectionsInCurrentThread(final String name, final String userName) { + if (dbPool == null) + return 0; + return dbPool.getConnectionsInCurrentThread(name, userName); + } + + /** + * Don't call it directly but use database.close(). + * + * @param iDatabase + */ + public void release(final DB iDatabase) { + if (dbPool != null) + dbPool.release(iDatabase); + } + + /** + * Closes the entire pool freeing all the connections. + */ + public void close() { + if (dbPool != null) { + dbPool.close(); + dbPool = null; + } + } + + /** + * Returns the maximum size of the pool + * + */ + public int getMaxSize() { + setup(); + return dbPool.getMaxSize(); + } + + /** + * Returns all the configured pools. + * + */ + public Map> getPools() { + return dbPool.getPools(); + } + + /** + * Removes a pool by name/user + * + */ + public void remove(final String iName, final String iUser) { + dbPool.remove(iName, iUser); + } + + @Override + public void run() { + close(); + } + + protected abstract DB createResource(Object owner, String iDatabaseName, Object... iAdditionalArgs); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePooled.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePooled.java new file mode 100644 index 00000000000..fffcda2851a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabasePooled.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +/** + * Basic interface for pooled database implementations. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ODatabasePooled { + + /** + * Reuses current instance. + * + * @param iOwner + * @param iAdditionalArgs + */ + public void reuse(final Object iOwner, final Object[] iAdditionalArgs); + + /** + * Tells if the underlying database is closed. + */ + public boolean isUnderlyingOpen(); + + /** + * Force closing the current instance avoiding to being reused. + */ + public void forceClose(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseRecordThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseRecordThreadLocal.java new file mode 100755 index 00000000000..d63d3a45cac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseRecordThreadLocal.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.exception.ODatabaseException; + +public class ODatabaseRecordThreadLocal extends ThreadLocal { + + public static volatile ODatabaseRecordThreadLocal INSTANCE = new ODatabaseRecordThreadLocal(); + + static { + final Orient inst = Orient.instance(); + inst.registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new ODatabaseRecordThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } + + @Override + public ODatabaseDocumentInternal get() { + ODatabaseDocumentInternal db = super.get(); + if (db == null) { + if (Orient.instance().getDatabaseThreadFactory() == null) { + throw new ODatabaseException( + "The database instance is not set in the current thread. Be sure to set it with: ODatabaseRecordThreadLocal.INSTANCE.set(db);"); + } else { + db = Orient.instance().getDatabaseThreadFactory().getThreadDatabase(); + if (db == null) { + throw new ODatabaseException( + "The database instance is not set in the current thread. Be sure to set it with: ODatabaseRecordThreadLocal.INSTANCE.set(db);"); + } else { + set(db); + } + } + } + return db; + } + + @Override + public void remove() { + super.remove(); + } + + @Override + public void set(final ODatabaseDocumentInternal value) { + super.set(value); + } + + public ODatabaseDocumentInternal getIfDefined() { + return super.get(); + } + + public boolean isDefined() { + return super.get() != null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSchemaAware.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSchemaAware.java new file mode 100644 index 00000000000..54100262bc8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSchemaAware.java @@ -0,0 +1,55 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +/** + * Generic interface for record based Database implementations with schema concept. + * + * @author Luca Garulli + * + */ +public interface ODatabaseSchemaAware extends ODatabase { + /** + * Creates a new entity instance. Each database implementation will return the right type. + * + * @return The new instance. + */ + public RET newInstance(String iClassName); + + /** + * Counts the entities contained in the specified class and sub classes (polymorphic). + * + * @param iClassName + * Class name + * @return Total entities + */ + public long countClass(String iClassName); + + /** + * Counts the entities contained in the specified class. + * + * @param iClassName + * Class name + * @param iPolymorphic + * True if consider also the sub classes, otherwise false + * @return Total entities + */ + public long countClass(String iClassName, final boolean iPolymorphic); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSessionMetadata.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSessionMetadata.java new file mode 100644 index 00000000000..d9904c78fc1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseSessionMetadata.java @@ -0,0 +1,7 @@ +package com.orientechnologies.orient.core.db; + +/** + * Created by tglman on 12/04/16. + */ +public interface ODatabaseSessionMetadata { +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseThreadLocalFactory.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseThreadLocalFactory.java new file mode 100644 index 00000000000..5fd3741495d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseThreadLocalFactory.java @@ -0,0 +1,26 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.db; + +/** + * @author luca.molino + * + */ +public interface ODatabaseThreadLocalFactory { + + ODatabaseDocumentInternal getThreadDatabase(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseWrapperAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseWrapperAbstract.java new file mode 100755 index 00000000000..62590536871 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/ODatabaseWrapperAbstract.java @@ -0,0 +1,381 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.cache.OLocalRecordCache; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.tool.ODatabaseImport; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.intent.OIntent; +import com.orientechnologies.orient.core.metadata.security.OToken; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.ORecordMetadata; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Callable; + +@SuppressWarnings("unchecked") +public abstract class ODatabaseWrapperAbstract implements ODatabaseInternal { + protected DB underlying; + protected ODatabaseInternal databaseOwner; + + public ODatabaseWrapperAbstract(final DB iDatabase) { + underlying = iDatabase; + databaseOwner = this; + } + + public THISDB open(final String iUserName, final String iUserPassword) { + underlying.open(iUserName, iUserPassword); + return (THISDB) this; + } + + public THISDB open(final OToken iToken) { + underlying.open(iToken); + return (THISDB) this; + } + + @Override + public ODatabase activateOnCurrentThread() { + return underlying.activateOnCurrentThread(); + } + + @Override + public boolean isActiveOnCurrentThread() { + return underlying.isActiveOnCurrentThread(); + } + + public THISDB create() { + return (THISDB) underlying.create(); + } + + @Override + public THISDB create(String incrementalBackupPath) { + return (THISDB) underlying.create(incrementalBackupPath); + } + + public THISDB create(final Map iInitialSettings) { + underlying.create(iInitialSettings); + return (THISDB) this; + } + + public boolean exists() { + return underlying.exists(); + } + + public void reload() { + underlying.reload(); + } + + @Override + public OContextConfiguration getConfiguration() { + return underlying.getConfiguration(); + } + + /** + * Executes a backup of the database. During the backup the database will be frozen in read-only mode. + * + * @param out OutputStream used to write the backup content. Use a FileOutputStream to make the backup persistent on + * disk + * @param options Backup options as Map object + * @param callable Callback to execute when the database is locked + * @param iListener Listener called for backup messages + * @param compressionLevel ZIP Compression level between 0 (no compression) and 9 (maximum). The bigger is the compression, the + * smaller will be the final backup content, but will consume more CPU and time to execute + * @param bufferSize Buffer size in bytes, the bigger is the buffer, the more efficient will be the compression + * + * @throws IOException + */ + @Override + public List backup(OutputStream out, Map options, Callable callable, + final OCommandOutputListener iListener, int compressionLevel, int bufferSize) throws IOException { + return underlying.backup(out, options, callable, iListener, compressionLevel, bufferSize); + } + + /** + * Executes a restore of a database backup. During the restore the database will be frozen in read-only mode. + * + * @param in InputStream used to read the backup content. Use a FileInputStream to read a backup on a disk + * @param options Backup options as Map object + * @param callable Callback to execute when the database is locked + * @param iListener Listener called for backup messages + * + * @throws IOException + * @see ODatabaseImport + */ + @Override + public void restore(InputStream in, Map options, Callable callable, + final OCommandOutputListener iListener) throws IOException { + underlying.restore(in, options, callable, iListener); + } + + public void close() { + underlying.close(); + } + + public void replaceStorage(OStorage iNewStorage) { + underlying.replaceStorage(iNewStorage); + } + + public void drop() { + underlying.drop(); + } + + public STATUS getStatus() { + return underlying.getStatus(); + } + + public THISDB setStatus(final STATUS iStatus) { + underlying.setStatus(iStatus); + return (THISDB) this; + } + + public String getName() { + return underlying.getName(); + } + + public String getURL() { + return underlying.getURL(); + } + + public OStorage getStorage() { + return underlying.getStorage(); + } + + public OLocalRecordCache getLocalCache() { + return underlying.getLocalCache(); + } + + public boolean isClosed() { + return underlying.isClosed(); + } + + public long countClusterElements(final int iClusterId) { + checkOpeness(); + return underlying.countClusterElements(iClusterId); + } + + /** + * {@inheritDoc} + */ + @Override + public void truncateCluster(String clusterName) { + checkOpeness(); + underlying.truncateCluster(clusterName); + } + + public long countClusterElements(final int[] iClusterIds) { + checkOpeness(); + return underlying.countClusterElements(iClusterIds); + } + + public long countClusterElements(final String iClusterName) { + checkOpeness(); + return underlying.countClusterElements(iClusterName); + } + + @Override + public long countClusterElements(int iClusterId, boolean countTombstones) { + checkOpeness(); + return underlying.countClusterElements(iClusterId, countTombstones); + } + + @Override + public long countClusterElements(int[] iClusterIds, boolean countTombstones) { + checkOpeness(); + return underlying.countClusterElements(iClusterIds, countTombstones); + } + + public int getClusters() { + checkOpeness(); + return underlying.getClusters(); + } + + public boolean existsCluster(String iClusterName) { + checkOpeness(); + return underlying.existsCluster(iClusterName); + } + + public Collection getClusterNames() { + checkOpeness(); + return underlying.getClusterNames(); + } + + public int getClusterIdByName(final String iClusterName) { + checkOpeness(); + return underlying.getClusterIdByName(iClusterName); + } + + public String getClusterNameById(final int iClusterId) { + checkOpeness(); + return underlying.getClusterNameById(iClusterId); + } + + public long getClusterRecordSizeById(int iClusterId) { + return underlying.getClusterRecordSizeById(iClusterId); + } + + public long getClusterRecordSizeByName(String iClusterName) { + return underlying.getClusterRecordSizeByName(iClusterName); + } + + public int addCluster(String iClusterName, int iRequestedId, Object... iParameters) { + checkOpeness(); + return underlying.addCluster(iClusterName, iRequestedId, iParameters); + } + + public int addCluster(final String iClusterName, final Object... iParameters) { + checkOpeness(); + return underlying.addCluster(iClusterName, iParameters); + } + + public Object alterCluster(String iClusterName, OCluster.ATTRIBUTES attribute, Object value) { + return underlying.alterCluster(iClusterName, attribute, value); + } + + public Object alterCluster(int iClusterId, OCluster.ATTRIBUTES attribute, Object value) { + return underlying.alterCluster(iClusterId, attribute, value); + } + + public boolean dropCluster(final String iClusterName, final boolean iTruncate) { + getLocalCache().freeCluster(getClusterIdByName(iClusterName)); + return underlying.dropCluster(iClusterName, iTruncate); + } + + public boolean dropCluster(final int iClusterId, final boolean iTruncate) { + getLocalCache().freeCluster(iClusterId); + return underlying.dropCluster(iClusterId, true); + } + + public int getDefaultClusterId() { + checkOpeness(); + return underlying.getDefaultClusterId(); + } + + public boolean declareIntent(final OIntent iIntent) { + return underlying.declareIntent(iIntent); + } + + public DBTYPE getUnderlying() { + return (DBTYPE) underlying; + } + + public ODatabaseInternal getDatabaseOwner() { + return databaseOwner; + } + + public ODatabaseInternal setDatabaseOwner(final ODatabaseInternal iOwner) { + databaseOwner = iOwner; + return this; + } + + @Override + public boolean equals(final Object iOther) { + if (!(iOther instanceof ODatabase)) + return false; + + final ODatabase other = (ODatabase) iOther; + + return other.getName().equals(getName()); + } + + @Override + public String toString() { + return underlying.toString(); + } + + public Object setProperty(final String iName, final Object iValue) { + return underlying.setProperty(iName, iValue); + } + + public Object getProperty(final String iName) { + return underlying.getProperty(iName); + } + + public Iterator> getProperties() { + return underlying.getProperties(); + } + + public Object get(final ATTRIBUTES iAttribute) { + return underlying.get(iAttribute); + } + + public THISDB set(final ATTRIBUTES attribute, final Object iValue) { + return (THISDB) underlying.set(attribute, iValue); + } + + public void registerListener(final ODatabaseListener iListener) { + underlying.registerListener(iListener); + } + + public void unregisterListener(final ODatabaseListener iListener) { + underlying.unregisterListener(iListener); + } + + @Override + public V callInLock(Callable iCallable, boolean iExclusiveLock) { + return getStorage().callInLock(iCallable, iExclusiveLock); + } + + @Override + public ORecordMetadata getRecordMetadata(ORID rid) { + return underlying.getRecordMetadata(rid); + } + + @Override + public long getSize() { + return underlying.getSize(); + } + + @Override + public void freeze(boolean throwException) { + underlying.freeze(throwException); + } + + @Override + public void freeze() { + underlying.freeze(); + } + + @Override + public boolean isFrozen() { + return underlying.isFrozen(); + } + + @Override + public void release() { + underlying.release(); + } + + protected void checkOpeness() { + if (isClosed()) + throw new ODatabaseException("Database '" + getURL() + "' is closed"); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OExecutionThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/db/OExecutionThreadLocal.java new file mode 100755 index 00000000000..53123400fac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OExecutionThreadLocal.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.common.thread.OSoftThread; +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.replication.OAsyncReplicationError; +import com.orientechnologies.orient.core.replication.OAsyncReplicationOk; + +/** + * Thread Local to store execution setting. + * + * @author Luca Garulli + */ +public class OExecutionThreadLocal extends ThreadLocal { + public class OExecutionThreadData { + volatile public OAsyncReplicationOk onAsyncReplicationOk; + volatile public OAsyncReplicationError onAsyncReplicationError; + } + + @Override + protected OExecutionThreadData initialValue() { + return new OExecutionThreadData(); + } + + public static volatile OExecutionThreadLocal INSTANCE = new OExecutionThreadLocal(); + + public static boolean isInterruptCurrentOperation() { + final Thread t = Thread.currentThread(); + if (t instanceof OSoftThread) + return ((OSoftThread) t).isShutdownFlag(); + return false; + } + + public void setInterruptCurrentOperation(final Thread t) { + if (t instanceof OSoftThread) + ((OSoftThread) t).softShutdown(); + } + + public static void setInterruptCurrentOperation() { + final Thread t = Thread.currentThread(); + if (t instanceof OSoftThread) + ((OSoftThread) t).softShutdown(); + } + + static { + final Orient inst = Orient.instance(); + inst.registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new OExecutionThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OHookReplacedRecordThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/db/OHookReplacedRecordThreadLocal.java new file mode 100644 index 00000000000..acadd851d0b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OHookReplacedRecordThreadLocal.java @@ -0,0 +1,59 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Uses Thread Local to store information used by hooks. + * + */ +public class OHookReplacedRecordThreadLocal extends ThreadLocal { + + public static volatile OHookReplacedRecordThreadLocal INSTANCE = new OHookReplacedRecordThreadLocal(); + + static { + Orient.instance().registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new OHookReplacedRecordThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } + + public ORecord getIfDefined() { + return super.get(); + } + + public boolean isDefined() { + return super.get() != null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePool.java b/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePool.java new file mode 100755 index 00000000000..45ee3cbe328 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePool.java @@ -0,0 +1,504 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.common.concur.lock.OInterruptedException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OStorageExistsException; +import com.orientechnologies.orient.core.metadata.security.OToken; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

      + * Database pool which has good multicore scalability characteristics because of creation of several partitions for each logical + * thread group which drastically decrease thread contention when we acquire new connection to database. + *

      + *

      + * To acquire connection from the pool call {@link #acquire()} method but to release connection you just need to call + * {@link com.orientechnologies.orient.core.db.document.ODatabaseDocument#close()} method. + *

      + *

      + * In case of remote storage database pool will keep connections to the remote storage till you close pool. So in case of remote + * storage you should close pool at the end of it's usage, it also may be closed on application shutdown but you should not rely on + * this behaviour. + *

      + *

      + * This pool has one noticeable difference from other pools. If you perform several subsequent acquire calls in the same thread the + * same instance of database will be returned, but amount of calls to close method should match to amount of acquire calls to + * release database back in the pool. It will allow you to use such feature as transaction propagation when you perform call of one + * service from another one. + *

      + *

      + * Given pool has two parameters now, amount of maximum connections for single partition and total amount of connections + * which may be hold by pool. When you start to use pool it will automatically split by several partitions, each partition is + * independent from other which gives us very good multicore scalability. + * Amount of partitions will be close to amount of cores but it is not mandatory and depends how much application is + * loaded. Amount of connections which may be hold by single partition is defined by user but we suggest to use default parameters + * if your application load is not extremely high. + *

      + * If total amount of connections which allowed to be hold by this pool is reached thread will wait till free connection will be + * available. If total amount of connection is set to value 0 or less it means that there is no connection limit. + *

      + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 06/11/14 + */ +public class OPartitionedDatabasePool extends OOrientListenerAbstract { + private static final int HASH_INCREMENT = 0x61c88647; + private static final int MIN_POOL_SIZE = 2; + private static final AtomicInteger nextHashCode = new AtomicInteger(); + protected final Map properties = new HashMap(); + private final String url; + private final String userName; + private final String password; + private final int maxPartitonSize; + private final AtomicBoolean poolBusy = new AtomicBoolean(); + private int maxPartitions = Runtime.getRuntime().availableProcessors() ; + private final Semaphore connectionsCounter; + private volatile ThreadLocal poolData = new ThreadPoolData(); + private volatile PoolPartition[] partitions; + private volatile boolean closed = false; + private boolean autoCreate = false; + + public OPartitionedDatabasePool(String url, String userName, String password) { + this(url, userName, password, Runtime.getRuntime().availableProcessors(), -1); + } + + public OPartitionedDatabasePool(String url, String userName, String password, int maxPartitionSize, int maxPoolSize) { + this.url = url; + this.userName = userName; + this.password = password; + if (maxPoolSize > 0) { + connectionsCounter = new Semaphore(maxPoolSize); + this.maxPartitions = 1; + this.maxPartitonSize = maxPoolSize; + } else { + this.maxPartitonSize = maxPartitionSize; + connectionsCounter = null; + } + + final PoolPartition[] pts = new PoolPartition[maxPartitions]; + + for (int i = 0; i < pts.length; i++) { + final PoolPartition partition = new PoolPartition(); + pts[i] = partition; + + initQueue(url, partition); + } + + partitions = pts; + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + private static int nextHashCode() { + return nextHashCode.getAndAdd(HASH_INCREMENT); + } + + public String getUrl() { + return url; + } + + public String getUserName() { + return userName; + } + + public int getMaxPartitonSize() { + return maxPartitonSize; + } + + public int getAvailableConnections() { + checkForClose(); + + int result = 0; + + for (PoolPartition partition : partitions) { + if (partition != null) { + result += partition.currentSize.get() - partition.acquiredConnections.get(); + } + } + + if (result < 0) + return 0; + + return result; + } + + public int getCreatedInstances() { + checkForClose(); + + int result = 0; + + for (PoolPartition partition : partitions) { + if (partition != null) { + result += partition.currentSize.get(); + } + } + + if (result < 0) + return 0; + + return result; + } + + public ODatabaseDocumentTx acquire() { + checkForClose(); + + final PoolData data = poolData.get(); + if (data.acquireCount > 0) { + data.acquireCount++; + + assert data.acquiredDatabase != null; + + final ODatabaseDocumentTx db = data.acquiredDatabase; + + db.activateOnCurrentThread(); + + for (Map.Entry entry : properties.entrySet()) { + db.setProperty(entry.getKey(), entry.getValue()); + } + return db; + } + + try { + if (connectionsCounter != null) + connectionsCounter.acquire(); + } catch (InterruptedException ie) { + throw OException.wrapException(new OInterruptedException("Acquiring of new connection was interrupted"), ie); + } + + boolean acquired = false; + try { + while (true) { + final PoolPartition[] pts = partitions; + + final int index = (pts.length - 1) & data.hashCode; + + PoolPartition partition = pts[index]; + if (partition == null) { + if (!poolBusy.get() && poolBusy.compareAndSet(false, true)) { + if (pts == partitions) { + partition = pts[index]; + + if (partition == null) { + partition = new PoolPartition(); + initQueue(url, partition); + pts[index] = partition; + } + } + + poolBusy.set(false); + } + + continue; + } else { + final DatabaseDocumentTxPooled db = partition.queue.poll(); + if (db == null) { + if (pts.length < maxPartitions) { + if (!poolBusy.get() && poolBusy.compareAndSet(false, true)) { + if (pts == partitions) { + final PoolPartition[] newPartitions = new PoolPartition[partitions.length << 1]; + System.arraycopy(partitions, 0, newPartitions, 0, partitions.length); + + partitions = newPartitions; + } + + poolBusy.set(false); + } + + continue; + } else { + if (partition.currentSize.get() >= maxPartitonSize) + throw new IllegalStateException("You have reached maximum pool size for given partition"); + + final DatabaseDocumentTxPooled newDb = new DatabaseDocumentTxPooled(url); + for (Map.Entry entry : properties.entrySet()) { + newDb.setProperty(entry.getKey(), entry.getValue()); + } + + openDatabase(newDb); + newDb.partition = partition; + + data.acquireCount = 1; + data.acquiredDatabase = newDb; + + partition.acquiredConnections.incrementAndGet(); + partition.currentSize.incrementAndGet(); + + acquired = true; + return newDb; + } + } else { + + for (Map.Entry entry : properties.entrySet()) { + db.setProperty(entry.getKey(), entry.getValue()); + } + + openDatabase(db); + db.partition = partition; + + partition.acquiredConnections.incrementAndGet(); + + data.acquireCount = 1; + data.acquiredDatabase = db; + + acquired = true; + return db; + } + } + } + } finally { + if (!acquired && connectionsCounter != null) + connectionsCounter.release(); + } + } + + public boolean isAutoCreate() { + return autoCreate; + } + + public OPartitionedDatabasePool setAutoCreate(final boolean autoCreate) { + this.autoCreate = autoCreate; + return this; + } + + public boolean isClosed() { + return closed; + } + + protected void openDatabase(final DatabaseDocumentTxPooled db) { + if (autoCreate) { + if (!db.getURL().startsWith("remote:") && !db.exists()) { + try { + db.create(); + } catch (OStorageExistsException ex) { + OLogManager.instance().debug(this, "Can not create storage " + db.getStorage() + " because it already exists."); + db.internalOpen(); + } + } else { + db.internalOpen(); + } + } else { + db.internalOpen(); + } + } + + @Override + public void onShutdown() { + close(); + } + + @Override + public void onStartup() { + if (poolData == null) + poolData = new ThreadPoolData(); + } + + public void close() { + if (closed) + return; + + closed = true; + + for (PoolPartition partition : partitions) { + if (partition == null) + continue; + + final Queue queue = partition.queue; + + while (!queue.isEmpty()) { + DatabaseDocumentTxPooled db = queue.poll(); + db.activateOnCurrentThread(); + OStorage storage = db.getStorage(); + storage.close(); + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } + } + + partitions = null; + poolData = null; + } + + private void initQueue(String url, PoolPartition partition) { + ConcurrentLinkedQueue queue = partition.queue; + + for (int n = 0; n < MIN_POOL_SIZE; n++) { + final DatabaseDocumentTxPooled db = new DatabaseDocumentTxPooled(url); + for (Map.Entry entry : properties.entrySet()) { + db.setProperty(entry.getKey(), entry.getValue()); + } + + queue.add(db); + } + + partition.currentSize.addAndGet(MIN_POOL_SIZE); + } + + private void checkForClose() { + if (closed) + throw new IllegalStateException("Pool is closed"); + } + + /** + * Sets a property value + * + * @param iName Property name + * @param iValue new value to set + * @return The previous value if any, otherwise null + */ + public Object setProperty(final String iName, final Object iValue) { + if (iValue != null) { + return properties.put(iName.toLowerCase(Locale.ENGLISH), iValue); + } else { + return properties.remove(iName.toLowerCase(Locale.ENGLISH)); + } + } + + /** + * Gets the property value. + * + * @param iName Property name + * @return The previous value if any, otherwise null + */ + public Object getProperty(final String iName) { + return properties.get(iName.toLowerCase(Locale.ENGLISH)); + } + + private static final class PoolData { + private final int hashCode; + private int acquireCount; + private DatabaseDocumentTxPooled acquiredDatabase; + + private PoolData() { + hashCode = nextHashCode(); + } + } + + private static class ThreadPoolData extends ThreadLocal { + @Override + protected PoolData initialValue() { + return new PoolData(); + } + } + + private static final class PoolPartition { + private final AtomicInteger currentSize = new AtomicInteger(); + private final AtomicInteger acquiredConnections = new AtomicInteger(); + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + } + + private final class DatabaseDocumentTxPooled extends ODatabaseDocumentTx { + private PoolPartition partition; + + private DatabaseDocumentTxPooled(String iURL) { + super(iURL, true); + } + + @Override + public DB open(OToken iToken) { + throw new ODatabaseException("Impossible to open a database managed by a pool "); + } + + @Override + public DB open(String iUserName, String iUserPassword) { + throw new ODatabaseException("Impossible to open a database managed by a pool "); + } + + /** + * @return true if database is obtained from the pool and false otherwise. + */ + @Override + public boolean isPooled() { + return true; + } + + protected void internalOpen() { + super.open(userName, password); + } + + @Override + public void close() { + if (poolData != null) { + final PoolData data = poolData.get(); + if (data.acquireCount == 0) + return; + + data.acquireCount--; + + if (data.acquireCount > 0) + return; + + PoolPartition p = partition; + partition = null; + + final OStorage storage = getStorage(); + if (storage == null) + return; + + //if connection is lost and storage is closed as result we should not put closed connection back to the pool + if (!storage.isClosed()) { + activateOnCurrentThread(); + super.close(); + + data.acquiredDatabase = null; + + p.queue.offer(this); + } else { + //close database instance but be ready that it will throw exception because of storage is closed + try { + super.close(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during closing of database % when storage %s was already closed", e, getUrl(), + storage.getName()); + } + + data.acquiredDatabase = null; + + //we create new connection instead of old one + final DatabaseDocumentTxPooled db = new DatabaseDocumentTxPooled(url); + p.queue.offer(db); + } + + if (connectionsCounter != null) + connectionsCounter.release(); + + p.acquiredConnections.decrementAndGet(); + } else { + super.close(); + } + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePoolFactory.java b/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePoolFactory.java new file mode 100644 index 00000000000..c9d3f93d4d3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OPartitionedDatabasePoolFactory.java @@ -0,0 +1,199 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.googlecode.concurrentlinkedhashmap.EvictionListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +/** + * Factory for {@link OPartitionedDatabasePool} pool, which also works as LRU cache with good mutlicore architecture support. + *

      + * In case of remote storage database pool will keep connections to the remote storage till you close pool. So in case of remote + * storage you should close pool factory at the end of it's usage, it also may be closed on application shutdown but you should not + * rely on this behaviour. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 06/11/14 + */ +public class OPartitionedDatabasePoolFactory extends OOrientListenerAbstract { + private volatile int maxPoolSize = 64; + private boolean closed = false; + + private final ConcurrentLinkedHashMap poolStore; + + private final EvictionListener evictionListener = new EvictionListener() { + @Override + public void onEviction(final PoolIdentity poolIdentity, final OPartitionedDatabasePool partitionedDatabasePool) { + partitionedDatabasePool.close(); + } + }; + + public OPartitionedDatabasePoolFactory() { + this(100); + } + + public OPartitionedDatabasePoolFactory(final int capacity) { + poolStore = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(capacity) + .listener(evictionListener).build(); + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + public int getMaxPoolSize() { + return maxPoolSize; + } + + public void setMaxPoolSize(int maxPoolSize) { + checkForClose(); + + this.maxPoolSize = maxPoolSize; + } + + public void reset() { + while (!poolStore.isEmpty()) { + final Iterator poolIterator = poolStore.values().iterator(); + + while (poolIterator.hasNext()) { + final OPartitionedDatabasePool pool = poolIterator.next(); + + try { + pool.close(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during pool close", e); + } + + poolIterator.remove(); + } + } + + for (OPartitionedDatabasePool pool : poolStore.values()) + pool.close(); + + poolStore.clear(); + } + + public OPartitionedDatabasePool get(final String url, final String userName, final String userPassword) { + if (url == null) + throw new IllegalArgumentException("url parameter is null"); + + checkForClose(); + + final PoolIdentity poolIdentity = new PoolIdentity(url, userName, userPassword); + + OPartitionedDatabasePool pool = poolStore.get(poolIdentity); + if (pool != null && !pool.isClosed()) + return pool; + + if (pool != null) + poolStore.remove(poolIdentity, pool); + + while (true) { + pool = new OPartitionedDatabasePool(url, userName, userPassword, 8, maxPoolSize); + + final OPartitionedDatabasePool oldPool = poolStore.putIfAbsent(poolIdentity, pool); + + if (oldPool != null) { + if (!oldPool.isClosed()) { + return oldPool; + } else { + poolStore.remove(poolIdentity, oldPool); + } + } else { + return pool; + } + } + + } + + public Collection getPools() { + checkForClose(); + + return Collections.unmodifiableCollection(poolStore.values()); + } + + public void close() { + if (closed) + return; + + closed = true; + reset(); + } + + private void checkForClose() { + if (closed) + throw new IllegalStateException("Pool factory is closed"); + } + + public boolean isClosed() { + return closed; + } + + @Override + public void onShutdown() { + close(); + } + + private static final class PoolIdentity { + private final String url; + private final String userName; + private final String userPassword; + + private PoolIdentity(final String url, final String userName, final String userPassword) { + this.url = url; + this.userName = userName; + this.userPassword = userPassword; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final PoolIdentity that = (PoolIdentity) o; + + if (!url.equals(that.url)) + return false; + if (!userName.equals(that.userName)) + return false; + if (!userPassword.equals(that.userPassword)) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = url.hashCode(); + result = 31 * result + userName.hashCode(); + result = 31 * result + userPassword.hashCode(); + return result; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OScenarioThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/db/OScenarioThreadLocal.java new file mode 100644 index 00000000000..c118eb38eaa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OScenarioThreadLocal.java @@ -0,0 +1,127 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; + +import java.util.concurrent.Callable; + +/** + * Thread local to know when the request comes from distributed requester avoiding loops. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OScenarioThreadLocal extends ThreadLocal { + public static volatile OScenarioThreadLocal INSTANCE = new OScenarioThreadLocal(); + + public enum RUN_MODE { + DEFAULT, RUNNING_DISTRIBUTED + } + + public static class RunContext { + public RUN_MODE runMode = RUN_MODE.DEFAULT; + public boolean inDatabaseLock; + } + + static { + Orient.instance().registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new OScenarioThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } + + public OScenarioThreadLocal() { + setRunMode(RUN_MODE.DEFAULT); + } + + public static Object executeAsDistributed(final Callable iCallback) { + final OScenarioThreadLocal.RUN_MODE currentDistributedMode = OScenarioThreadLocal.INSTANCE.getRunMode(); + if (currentDistributedMode != OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED) + // ASSURE SCHEMA CHANGES ARE NEVER PROPAGATED ON CLUSTER + OScenarioThreadLocal.INSTANCE.setRunMode(OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED); + + try { + return iCallback.call(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (currentDistributedMode != OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED) + // RESTORE PREVIOUS MODE + OScenarioThreadLocal.INSTANCE.setRunMode(OScenarioThreadLocal.RUN_MODE.DEFAULT); + } + } + + public static Object executeAsDefault(final Callable iCallback) { + final OScenarioThreadLocal.RUN_MODE currentDistributedMode = OScenarioThreadLocal.INSTANCE.getRunMode(); + if (currentDistributedMode == OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED) + // ASSURE SCHEMA CHANGES ARE NEVER PROPAGATED ON CLUSTER + OScenarioThreadLocal.INSTANCE.setRunMode(RUN_MODE.DEFAULT); + + try { + return (T) iCallback.call(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (currentDistributedMode == OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED) + // RESTORE PREVIOUS MODE + OScenarioThreadLocal.INSTANCE.setRunMode(OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED); + } + } + + public void setRunMode(final RUN_MODE value) { + final RunContext context = get(); + context.runMode = value; + } + + public void setInDatabaseLock(final boolean value) { + final RunContext context = get(); + context.inDatabaseLock = value; + } + + public RUN_MODE getRunMode() { + return get().runMode; + } + + public boolean isRunModeDistributed() { + return get().runMode == RUN_MODE.RUNNING_DISTRIBUTED; + } + + public boolean isInDatabaseLock() { + return get().inDatabaseLock; + } + + @Override + protected RunContext initialValue() { + return new RunContext(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/OUserObject2RecordHandler.java b/core/src/main/java/com/orientechnologies/orient/core/db/OUserObject2RecordHandler.java new file mode 100644 index 00000000000..824c83e5e61 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/OUserObject2RecordHandler.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Basic interface to handle the mapping between user objects and records. In some database implementation the user objects can be + * the records themselves. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OUserObject2RecordHandler { + /** + * Returns the record associated to a user object. If iCreateIfNotAvailable is true, then a new record instance will be created + * transparently. + * + * @param iUserObject + * User object + * @param iCreateIfNotAvailable + * Create the record if not available + * @return The record associated + */ + ORecord getRecordByUserObject(Object iUserObject, boolean iCreateIfNotAvailable); + + /** + * Returns the user object associated to a record. If the record is not loaded yet, iFetchPlan will be used as fetch plan. + * + * @param iRecord + * Record + * @param iFetchPlan + * If the record is not loaded yet, use this as fetch plan + * @return The user object associated + */ + Object getUserObjectByRecord(OIdentifiable iRecord, String iFetchPlan); + + /** + * Tells if e user object exists for a certain RecordId. + */ + boolean existsUserObjectByRID(ORID iRID); + + /** + * Registers the association between a user object and a record. + * + * @param iUserObject + * User object + * @param iRecord + * record + */ + void registerUserObject(final Object iUserObject, final ORecord iRecord); + + /** + * Registers the saved linked record. Needed only to make the old object database implementation work + * + * @param iRecord + * record + */ + void registerUserObjectAfterLinkSave(final ORecord iRecord); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocument.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocument.java new file mode 100755 index 00000000000..1dfc00382d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocument.java @@ -0,0 +1,303 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseSchemaAware; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.intent.OIntent; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorCluster; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Generic interface for document based Database implementations. + * + * @author Luca Garulli + */ +public interface ODatabaseDocument extends ODatabase, ODatabaseSchemaAware { + + final static String TYPE = "document"; + + /** + * Browses all the records of the specified class and also all the subclasses. If you've a class Vehicle and Car that extends + * Vehicle then a db.browseClass("Vehicle", true) will return all the instances of Vehicle and Car. The order of the returned + * instance starts from record id with position 0 until the end. Base classes are worked at first. + * + * @param iClassName + * Class name to iterate + * @return Iterator of ODocument instances + */ + ORecordIteratorClass browseClass(String iClassName); + + /** + * Browses all the records of the specified class and if iPolymorphic is true also all the subclasses. If you've a class Vehicle + * and Car that extends Vehicle then a db.browseClass("Vehicle", true) will return all the instances of Vehicle and Car. The order + * of the returned instance starts from record id with position 0 until the end. Base classes are worked at first. + * + * @param iClassName + * Class name to iterate + * @param iPolymorphic + * Consider also the instances of the subclasses or not + * @return Iterator of ODocument instances + */ + ORecordIteratorClass browseClass(String iClassName, boolean iPolymorphic); + + /** + * Flush all indexes and cached storage content to the disk. + * + * After this call users can perform only select queries. All write-related commands will queued till {@link #release()} command + * will be called. + * + * Given command waits till all on going modifications in indexes or DB will be finished. + * + * IMPORTANT: This command is not reentrant. + */ + void freeze(); + + /** + * Allows to execute write-related commands on DB. Called after {@link #freeze()} command. + */ + void release(); + + /** + * Flush all indexes and cached storage content to the disk. + * + * After this call users can perform only select queries. All write-related commands will queued till {@link #release()} command + * will be called or exception will be thrown on attempt to modify DB data. Concrete behaviour depends on + * throwException parameter. + * + * IMPORTANT: This command is not reentrant. + * + * @param throwException + * If true {@link com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException} + * exception will be thrown in case of write command will be performed. + */ + void freeze(boolean throwException); + + /** + * Browses all the records of the specified cluster. + * + * @param iClusterName + * Cluster name to iterate + * @return Iterator of ODocument instances + */ + ORecordIteratorCluster browseCluster(String iClusterName); + + ORecordIteratorCluster browseCluster(String iClusterName, long startClusterPosition, + long endClusterPosition, boolean loadTombstones); + + /** + * Browses all the records of the specified cluster of the passed record type. + * + * @param iClusterName + * Cluster name to iterate + * @param iRecordClass + * The record class expected + * @return Iterator of ODocument instances + */ + ORecordIteratorCluster browseCluster(String iClusterName, Class iRecordClass); + + ORecordIteratorCluster browseCluster(String iClusterName, Class iRecordClass, + long startClusterPosition, long endClusterPosition); + + @Deprecated + ORecordIteratorCluster browseCluster(String iClusterName, Class iRecordClass, + long startClusterPosition, long endClusterPosition, boolean loadTombstones); + + /** + * Returns the record for a OIdentifiable instance. If the argument received already is a ORecord instance, then it's returned as + * is, otherwise a new ORecord is created with the identity received and returned. + * + * @param iIdentifiable + * @return A ORecord instance + */ + RET getRecord(OIdentifiable iIdentifiable); + + /** + * Returns the default record type for this kind of database. + */ + byte getRecordType(); + + /** + * Returns true if current configuration retains objects, otherwise false + * + * @see #setRetainRecords(boolean) + */ + boolean isRetainRecords(); + + /** + * Specifies if retain handled objects in memory or not. Setting it to false can improve performance on large inserts. Default is + * enabled. + * + * @param iValue + * True to enable, false to disable it. + * @see #isRetainRecords() + */ + ODatabaseDocument setRetainRecords(boolean iValue); + + /** + * Checks if the operation on a resource is allowed for the current user. + * + * @param resourceGeneric + * Generic Resource where to execute the operation + * @param resourceGeneric + * Specific resource name where to execute the operation + * @param iOperation + * Operation to execute against the resource + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB checkSecurity(ORule.ResourceGeneric resourceGeneric, String resourceSpecific, int iOperation); + + /** + * Checks if the operation on a resource is allowed for the current user. The check is made in two steps: + *

        + *
      1. + * Access to all the resource as *
      2. + *
      3. + * Access to the specific target resource
      4. + *
      + * + * @param iResourceGeneric + * Resource where to execute the operation, i.e.: database.clusters + * @param iOperation + * Operation to execute against the resource + * @param iResourceSpecific + * Target resource, i.e.: "employee" to specify the cluster name. + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB checkSecurity(ORule.ResourceGeneric iResourceGeneric, int iOperation, Object iResourceSpecific); + + /** + * Checks if the operation against multiple resources is allowed for the current user. The check is made in two steps: + *
        + *
      1. + * Access to all the resource as *
      2. + *
      3. + * Access to the specific target resources
      4. + *
      + * + * @param iResourceGeneric + * Resource where to execute the operation, i.e.: database.clusters + * @param iOperation + * Operation to execute against the resource + * @param iResourcesSpecific + * Target resources as an array of Objects, i.e.: ["employee", 2] to specify cluster name and id. + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB checkSecurity(ORule.ResourceGeneric iResourceGeneric, int iOperation, + Object... iResourcesSpecific); + + /** + * Tells if validation of record is active. Default is true. + * + * @return true if it's active, otherwise false. + */ + boolean isValidationEnabled(); + + /** + * Enables or disables the record validation. + * + * Since 2.2 this setting is persistent. + * + * @param iEnabled + * True to enable, false to disable + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + DB setValidationEnabled(boolean iEnabled); + + /** + * Checks if the operation on a resource is allowed for the current user. + * + * @param iResource + * Resource where to execute the operation + * @param iOperation + * Operation to execute against the resource + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + @Deprecated + DB checkSecurity(String iResource, int iOperation); + + /** + * Checks if the operation on a resource is allowed for the current user. The check is made in two steps: + *
        + *
      1. + * Access to all the resource as *
      2. + *
      3. + * Access to the specific target resource
      4. + *
      + * + * @param iResourceGeneric + * Resource where to execute the operation, i.e.: database.clusters + * @param iOperation + * Operation to execute against the resource + * @param iResourceSpecific + * Target resource, i.e.: "employee" to specify the cluster name. + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + @Deprecated + DB checkSecurity(String iResourceGeneric, int iOperation, Object iResourceSpecific); + + /** + * Checks if the operation against multiple resources is allowed for the current user. The check is made in two steps: + *
        + *
      1. + * Access to all the resource as *
      2. + *
      3. + * Access to the specific target resources
      4. + *
      + * + * @param iResourceGeneric + * Resource where to execute the operation, i.e.: database.clusters + * @param iOperation + * Operation to execute against the resource + * @param iResourcesSpecific + * Target resources as an array of Objects, i.e.: ["employee", 2] to specify cluster name and id. + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + @Deprecated + DB checkSecurity(String iResourceGeneric, int iOperation, Object... iResourcesSpecific); + + /** + * @return true if database is obtained from the pool and false otherwise. + */ + boolean isPooled(); + + /** + * Add a cluster for blob records. + * + * @param iClusterName Cluster name + * @param iParameters Additional parameters to pass to the factories + * + * @return Cluster id + */ + int addBlobCluster(String iClusterName, Object... iParameters); + + /** + * Return the active intent. + * + * @return + */ + OIntent getActiveIntent(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentPool.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentPool.java new file mode 100644 index 00000000000..1b5487d1dc1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentPool.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.orient.core.db.ODatabasePoolBase; + +/** + * @deprecated use {@link com.orientechnologies.orient.core.db.OPartitionedDatabasePool} or + * {@link com.orientechnologies.orient.core.db.OPartitionedDatabasePoolFactory} instead. + */ +@Deprecated +public class ODatabaseDocumentPool extends ODatabasePoolBase { + + private static ODatabaseDocumentPool globalInstance = new ODatabaseDocumentPool(); + + public ODatabaseDocumentPool() { + super(); + } + + public ODatabaseDocumentPool(final String iURL, final String iUserName, final String iUserPassword) { + super(iURL, iUserName, iUserPassword); + } + + public static ODatabaseDocumentPool global() { + globalInstance.setup(); + return globalInstance; + } + + public static ODatabaseDocumentPool global(final int iPoolMin, final int iPoolMax) { + globalInstance.setup(iPoolMin, iPoolMax); + return globalInstance; + } + + @Override + protected ODatabaseDocumentTx createResource(Object owner, String iDatabaseName, Object... iAdditionalArgs) { + return new ODatabaseDocumentTxPooled((ODatabaseDocumentPool) owner, iDatabaseName, (String) iAdditionalArgs[0], + (String) iAdditionalArgs[1]); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTx.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTx.java new file mode 100755 index 00000000000..861c1fb0bdb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTx.java @@ -0,0 +1,3469 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.listener.OListenerManger; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.cache.OCommandCacheHook; +import com.orientechnologies.orient.core.cache.OLocalRecordCache; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestInternal; +import com.orientechnologies.orient.core.config.OContextConfiguration; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.config.OStorageClusterConfiguration; +import com.orientechnologies.orient.core.conflict.ORecordConflictStrategy; +import com.orientechnologies.orient.core.db.*; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.ORidBagDeleter; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeCollectionManager; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.exception.*; +import com.orientechnologies.orient.core.fetch.OFetchHelper; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.ClassIndexManagerRemote; +import com.orientechnologies.orient.core.index.OClassIndexManager; +import com.orientechnologies.orient.core.intent.OIntent; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorCluster; +import com.orientechnologies.orient.core.metadata.OMetadata; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.function.OFunctionTrigger; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OSchemaProxy; +import com.orientechnologies.orient.core.metadata.security.*; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceTrigger; +import com.orientechnologies.orient.core.query.OQuery; +import com.orientechnologies.orient.core.query.live.OLiveQueryHook; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.ORecordVersionHelper; +import com.orientechnologies.orient.core.record.impl.OBlob; +import com.orientechnologies.orient.core.record.impl.ODirtyManager; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.schedule.OSchedulerTrigger; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSaveThreadLocal; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.parser.OStatement; +import com.orientechnologies.orient.core.storage.*; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.OFreezableStorageComponent; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OOfflineClusterException; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext; +import com.orientechnologies.orient.core.tx.OTransaction; +import com.orientechnologies.orient.core.tx.OTransactionNoTx; +import com.orientechnologies.orient.core.tx.OTransactionOptimistic; +import com.orientechnologies.orient.core.tx.OTransactionRealAbstract; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Document API entrypoint. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class ODatabaseDocumentTx extends OListenerManger implements ODatabaseDocumentInternal { + + @Deprecated + private static final String DEF_RECORD_FORMAT = "csv"; + protected static ORecordSerializer defaultSerializer; + + static { + defaultSerializer = ORecordSerializerFactory.instance() + .getFormat(OGlobalConfiguration.DB_DOCUMENT_SERIALIZER.getValueAsString()); + if (defaultSerializer == null) + throw new ODatabaseException( + "Impossible to find serializer with name " + OGlobalConfiguration.DB_DOCUMENT_SERIALIZER.getValueAsString()); + } + + private final Map properties = new HashMap(); + private final Map unmodifiableHooks; + private final Set inHook = new HashSet(); + protected ORecordSerializer serializer; + private String url; + private OStorage storage; + private STATUS status; + private OIntent currentIntent; + private ODatabaseInternal databaseOwner; + private OMetadataDefault metadata; + private OImmutableUser user; + private byte recordType; + @Deprecated + private String recordFormat; + private final Map hooks = new LinkedHashMap(); + private boolean retainRecords = true; + private OLocalRecordCache localCache; + private boolean mvcc; + private OCurrentStorageComponentsFactory componentsFactory; + private boolean initialized = false; + private OTransaction currentTx; + private boolean keepStorageOpen = false; + private final AtomicReference owner = new AtomicReference(); + + private boolean prefetchRecords = false; + + protected ODatabaseSessionMetadata sessionMetadata; + + private final ORecordHook[][] hooksByScope = new ORecordHook[ORecordHook.SCOPE.values().length][]; + + /** + * Creates a new connection to the database. + * + * @param iURL of the database + */ + public ODatabaseDocumentTx(final String iURL) { + this(iURL, false); + } + + public ODatabaseDocumentTx(final String iURL, boolean keepStorageOpen) { + super(false); + + if (iURL == null) + throw new IllegalArgumentException("URL parameter is null"); + + activateOnCurrentThread(); + + try { + this.keepStorageOpen = keepStorageOpen; + url = iURL.replace('\\', '/'); + status = STATUS.CLOSED; + + // SET DEFAULT PROPERTIES + setProperty("fetch-max", 50); + + storage = Orient.instance().loadStorage(url); + + // OVERWRITE THE URL + url = storage.getURL(); + + unmodifiableHooks = Collections.unmodifiableMap(hooks); + + recordType = ODocument.RECORD_TYPE; + localCache = new OLocalRecordCache(); + + mvcc = OGlobalConfiguration.DB_MVCC.getValueAsBoolean(); + + init(); + + databaseOwner = this; + } catch (Exception t) { + if (storage != null) + Orient.instance().unregisterStorage(storage); + ODatabaseRecordThreadLocal.INSTANCE.remove(); + + throw OException.wrapException(new ODatabaseException("Error on opening database '" + iURL + "'"), t); + } + + setSerializer(defaultSerializer); + } + + /** + * @return default serializer which is used to serialize documents. Default serializer is common for all database instances. + */ + public static ORecordSerializer getDefaultSerializer() { + return defaultSerializer; + } + + /** + * Sets default serializer. The default serializer is common for all database instances. + * + * @param iDefaultSerializer new default serializer value + */ + public static void setDefaultSerializer(ORecordSerializer iDefaultSerializer) { + defaultSerializer = iDefaultSerializer; + } + + /** + * Opens connection to the storage with given user and password. + *

      + * But we do suggest {@link com.orientechnologies.orient.core.db.OPartitionedDatabasePool#acquire()} instead. It will make work + * faster even with embedded database. + * + * @param iUserName Username to login + * @param iUserPassword Password associated to the user + * + * @return Current database instance. + */ + @Override + public DB open(final String iUserName, final String iUserPassword) { + boolean failure = true; + setupThreadOwner(); + activateOnCurrentThread(); + + try { + if (status == STATUS.OPEN) + throw new IllegalStateException("Database " + getName() + " is already open"); + + if (user != null && !user.getName().equals(iUserName)) + initialized = false; + + final String encKey = (String) getProperty(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY.getKey()); + String currKey = null; + + if (storage.getConfiguration() != null && storage.getConfiguration().getContextConfiguration() != null) { + currKey = (String) storage.getConfiguration().getContextConfiguration() + .getValue(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY); + + // If an encryption key is set as a database property, and + // the storage engine is open and has an encryption key value, and + // the two encryption keys differ, force the storage closed so that the + // new encryption key in properties will be used. + if (encKey != null && currKey != null && !encKey.equals(currKey)) { + // If the storage is open... + if (!storage.isClosed()) { + storage.close(true, false); // force it closed + } + } + } + + if (storage.isClosed()) { + storage.open(iUserName, iUserPassword, properties); + initialized = false; + } else if (storage instanceof OStorageProxy) { + final String name = ((OStorageProxy) storage).getUserName(); + if (name == null || !name.equals(iUserName)) { + storage.close(); + storage.open(iUserName, iUserPassword, properties); + } + } + + status = STATUS.OPEN; + + initAtFirstOpen(iUserName, iUserPassword); + + if (!(getStorage() instanceof OStorageProxy)) { + final OSecurity security = metadata.getSecurity(); + if (user == null || user.getVersion() != security.getVersion() || !user.getName().equalsIgnoreCase(iUserName)) { + final OUser usr = metadata.getSecurity().authenticate(iUserName, iUserPassword); + if (usr != null) + user = new OImmutableUser(security.getVersion(), usr); + else + user = null; + + checkSecurity(ORule.ResourceGeneric.DATABASE, ORole.PERMISSION_READ); + } + } + + // WAKE UP LISTENERS + callOnOpenListeners(); + + failure = false; + } catch (OException e) { + close(); + throw e; + } catch (Exception e) { + close(); + throw OException.wrapException(new ODatabaseException("Cannot open database url=" + getURL()), e); + } finally { + if (failure) + owner.set(null); + } + return (DB) this; + } + + public boolean isPrefetchRecords() { + return prefetchRecords; + } + + public void setPrefetchRecords(boolean prefetchRecords) { + this.prefetchRecords = prefetchRecords; + } + + /** + * Opens a database using an authentication token received as an argument. + * + * @param iToken Authentication token + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + public DB open(final OToken iToken) { + boolean failure = true; + + setupThreadOwner(); + activateOnCurrentThread(); + + try { + if (status == STATUS.OPEN) + throw new IllegalStateException("Database " + getName() + " is already open"); + + if (user != null && !user.getIdentity().equals(iToken.getUserId())) + initialized = false; + + if (storage instanceof OStorageProxy) { + throw new ODatabaseException("Cannot use a token open on remote database"); + } + if (storage.isClosed()) { + // i don't have username and password at this level, anyway the storage embedded don't really need it + storage.open("", "", properties); + } + + status = STATUS.OPEN; + + initAtFirstOpen(null, null); + + final OSecurity security = metadata.getSecurity(); + if (user == null || user.getVersion() != security.getVersion()) { + final OUser usr = metadata.getSecurity().authenticate(iToken); + if (usr != null) + user = new OImmutableUser(security.getVersion(), usr); + else + user = null; + + checkSecurity(ORule.ResourceGeneric.DATABASE, ORole.PERMISSION_READ); + } + + // WAKE UP LISTENERS + callOnOpenListeners(); + + failure = false; + } catch (OException e) { + close(); + throw e; + } catch (Exception e) { + close(); + throw OException.wrapException(new ODatabaseException("Cannot open database"), e); + } finally { + if (failure) + owner.set(null); + } + return (DB) this; + } + + private void setupThreadOwner() { + final Thread current = Thread.currentThread(); + final Thread o = owner.get(); + + if (o != null || !owner.compareAndSet(null, current)) { + throw new IllegalStateException("Current instance is owned by other thread '" + (o != null ? o.getName() : "?") + "'"); + } + } + + public void callOnOpenListeners() { + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onOpen(getDatabaseOwner()); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : getListenersCopy()) + try { + listener.onOpen(getDatabaseOwner()); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public DB create() { + return create((Map) null); + } + + /** + * {@inheritDoc} + */ + @Override + public DB create(String incrementalBackupPath) { + create(); + + final OStorage storage = getStorage(); + storage.restoreFromIncrementalBackup(incrementalBackupPath); + + metadata = new OMetadataDefault(this); + metadata.load(); + + return (DB) this; + } + + @Override + public DB create(final Map iInitialSettings) { + setupThreadOwner(); + activateOnCurrentThread(); + + try { + if (status == STATUS.OPEN) + throw new IllegalStateException("Database " + getName() + " is already open"); + + if (storage == null) + storage = Orient.instance().loadStorage(url); + + final OContextConfiguration ctxCfg = storage.getConfiguration().getContextConfiguration(); + if (iInitialSettings != null && !iInitialSettings.isEmpty()) { + // SETUP INITIAL SETTINGS + for (Map.Entry e : iInitialSettings.entrySet()) { + ctxCfg.setValue(e.getKey(), e.getValue()); + } + storage.getConfiguration().update(); + } + + storage.create(properties); + + status = STATUS.OPEN; + + componentsFactory = getStorage().getComponentsFactory(); + + localCache.startup(); + + getStorage().getConfiguration().setRecordSerializer(getSerializer().toString()); + getStorage().getConfiguration().setRecordSerializerVersion(getSerializer().getCurrentVersion()); + + // since 2.1 newly created databases use strict SQL validation by default + getStorage().getConfiguration().setProperty(OStatement.CUSTOM_STRICT_SQL, "true"); + + getStorage().getConfiguration().update(); + + // THIS IF SHOULDN'T BE NEEDED, CREATE HAPPEN ONLY IN EMBEDDED + if (!(getStorage() instanceof OStorageProxy)) + installHooksEmbedded(); + + // CREATE THE DEFAULT SCHEMA WITH DEFAULT USER + metadata = new OMetadataDefault(this); + metadata.create(); + + if (!(getStorage() instanceof OStorageProxy)) + registerHook(new OCommandCacheHook(this), ORecordHook.HOOK_POSITION.REGULAR); + + registerHook(new OSecurityTrackerHook(metadata.getSecurity(), this), ORecordHook.HOOK_POSITION.LAST); + + final OUser usr = getMetadata().getSecurity().getUser(OUser.ADMIN); + + if (usr == null) + user = null; + else + user = new OImmutableUser(getMetadata().getSecurity().getVersion(), usr); + + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onCreate(getDatabaseOwner()); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onCreate(this); + } catch (Throwable ignore) { + } + } catch (OStorageExistsException e) { + status = STATUS.CLOSED; + owner.set(null); + + throw OException.wrapException(new ODatabaseException("Cannot create database '" + getName() + "'"), e); + } catch (Exception e) { + // REMOVE THE (PARTIAL) DATABASE + try { + drop(); + } catch (Exception ex) { + // IGNORE IT + } + + // DELETE THE STORAGE TOO + try { + if (storage == null) + storage = Orient.instance().loadStorage(url); + storage.delete(); + } catch (Exception ex) { + // IGNORE IT + } + + status = STATUS.CLOSED; + owner.set(null); + + throw OException.wrapException(new ODatabaseException("Cannot create database '" + getName() + "'"), e); + } + return (DB) this; + } + + /** + * {@inheritDoc} + */ + @Override + public void drop() { + checkOpeness(); + checkIfActive(); + + checkSecurity(ORule.ResourceGeneric.DATABASE, ORole.PERMISSION_DELETE); + + callOnDropListeners(); + + if (metadata != null) { + metadata.close(); + metadata = null; + } + + closeOnDelete(); + + try { + if (storage == null) + storage = Orient.instance().loadStorage(url); + + storage.delete(); + storage = null; + + status = STATUS.CLOSED; + ODatabaseRecordThreadLocal.INSTANCE.remove(); + clearOwner(); + + } catch (OException e) { + // PASS THROUGH + throw e; + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Cannot delete database"), e); + } + } + + /** + * Returns a copy of current database if it's open. The returned instance can be used by another thread without affecting current + * instance. The database copy is not set in thread local. + */ + public ODatabaseDocumentTx copy() { + ODatabaseDocumentInternal dbInThreadLocal = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (this.isClosed()) + throw new ODatabaseException("Cannot copy a closed db"); + + final ODatabaseDocumentTx db = new ODatabaseDocumentTx(this.url); + db.setupThreadOwner(); + + db.user = this.user; + db.properties.putAll(this.properties); + db.serializer = this.serializer; + + db.componentsFactory = this.componentsFactory; + db.metadata = new OMetadataDefault(db); + + db.initialized = true; + if (storage instanceof OStorageProxy) { + db.storage = ((OStorageProxy) storage).copy(this, db); + ((OStorageProxy) db.storage).addUser(); + } else { + db.storage = storage; + } + + db.setStatus(STATUS.OPEN); + db.metadata.load(); + + if (!(db.getStorage() instanceof OStorageProxy)) + db.installHooksEmbedded(); + else + db.installHooksRemote(); + + db.initialized = true; + + if (dbInThreadLocal != null) { + dbInThreadLocal.activateOnCurrentThread(); + } else { + if (ODatabaseRecordThreadLocal.INSTANCE.isDefined()) { + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } + } + + return db; + } + + public void callOnCloseListeners() { + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onClose(getDatabaseOwner()); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : getListenersCopy()) + try { + listener.onClose(getDatabaseOwner()); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public void callOnDropListeners() { + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) { + activateOnCurrentThread(); + it.next().onDrop(getDatabaseOwner()); + } + + // WAKE UP LISTENERS + for (ODatabaseListener listener : getListenersCopy()) + try { + activateOnCurrentThread(); + listener.onDelete(getDatabaseOwner()); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * {@inheritDoc} + */ + public RET getRecord(final OIdentifiable iIdentifiable) { + if (iIdentifiable instanceof ORecord) + return (RET) iIdentifiable; + return (RET) load(iIdentifiable.getIdentity()); + } + + @Override + public void reload() { + checkIfActive(); + + if (this.isClosed()) + throw new ODatabaseException("Cannot reload a closed db"); + + storage.reload(); + metadata.reload(); + } + + /** + * {@inheritDoc} + */ + public RET load(final ORID iRecordId, final String iFetchPlan, final boolean iIgnoreCache) { + return (RET) executeReadRecord((ORecordId) iRecordId, null, -1, iFetchPlan, iIgnoreCache, !iIgnoreCache, false, + OStorage.LOCKING_STRATEGY.DEFAULT, new SimpleRecordReader(prefetchRecords)); + } + + /** + * Deletes the record checking the version. + */ + public ODatabase delete(final ORID iRecord, final int iVersion) { + executeDeleteRecord(iRecord, iVersion, true, OPERATION_MODE.SYNCHRONOUS, false); + return this; + } + + public ODatabase cleanOutRecord(final ORID iRecord, final int iVersion) { + executeDeleteRecord(iRecord, iVersion, true, OPERATION_MODE.SYNCHRONOUS, true); + return this; + } + + public String getType() { + return TYPE; + } + + /** + * Deletes the record without checking the version. + */ + public ODatabaseDocument delete(final ORID iRecord, final OPERATION_MODE iMode) { + ORecord record = iRecord.getRecord(); + if (record == null) + return this; + + delete(record, iMode); + return this; + } + + public ODatabaseDocument delete(final ORecord iRecord, final OPERATION_MODE iMode) { + checkIfActive(); + currentTx.deleteRecord(iRecord, iMode); + return this; + } + + public ORecordIteratorCluster browseCluster(final String iClusterName, final Class iClass) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + + checkIfActive(); + + final int clusterId = getClusterIdByName(iClusterName); + + return new ORecordIteratorCluster(this, this, clusterId); + } + + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public ORecordIteratorCluster browseCluster(final String iClusterName, final Class iRecordClass, + final long startClusterPosition, final long endClusterPosition, final boolean loadTombstones) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + checkIfActive(); + + final int clusterId = getClusterIdByName(iClusterName); + + return new ORecordIteratorCluster(this, this, clusterId, startClusterPosition, endClusterPosition, loadTombstones, + OStorage.LOCKING_STRATEGY.DEFAULT); + } + + @Override + public ORecordIteratorCluster browseCluster(String iClusterName, Class iRecordClass, + long startClusterPosition, long endClusterPosition) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + checkIfActive(); + + final int clusterId = getClusterIdByName(iClusterName); + + return new ORecordIteratorCluster(this, this, clusterId, startClusterPosition, endClusterPosition); + } + + /** + * {@inheritDoc} + */ + public OCommandRequest command(final OCommandRequest iCommand) { + checkSecurity(ORule.ResourceGeneric.COMMAND, ORole.PERMISSION_READ); + checkIfActive(); + + final OCommandRequestInternal command = (OCommandRequestInternal) iCommand; + + try { + command.reset(); + return command; + + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Error on command execution"), e); + } + } + + /** + * {@inheritDoc} + */ + public > RET query(final OQuery iCommand, final Object... iArgs) { + checkIfActive(); + iCommand.reset(); + return (RET) iCommand.execute(iArgs); + } + + /** + * {@inheritDoc} + */ + public byte getRecordType() { + return recordType; + } + + /** + * {@inheritDoc} + */ + @Override + public long countClusterElements(final int[] iClusterIds) { + return countClusterElements(iClusterIds, false); + } + + /** + * {@inheritDoc} + */ + @Override + public long countClusterElements(final int iClusterId) { + return countClusterElements(iClusterId, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void truncateCluster(String clusterName) { + command(new OCommandSQL("truncate cluster " + clusterName)).execute(); + } + + /** + * {@inheritDoc} + */ + @Override + public long countClusterElements(int iClusterId, boolean countTombstones) { + final String name = getClusterNameById(iClusterId); + if (name == null) + return 0; + + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, name); + checkIfActive(); + + return storage.count(iClusterId, countTombstones); + } + + /** + * {@inheritDoc} + */ + @Override + public long countClusterElements(int[] iClusterIds, boolean countTombstones) { + checkIfActive(); + String name; + for (int iClusterId : iClusterIds) { + name = getClusterNameById(iClusterId); + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, name); + } + + return storage.count(iClusterIds, countTombstones); + } + + /** + * {@inheritDoc} + */ + @Override + public long countClusterElements(final String iClusterName) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + checkIfActive(); + + final int clusterId = getClusterIdByName(iClusterName); + if (clusterId < 0) + throw new IllegalArgumentException("Cluster '" + iClusterName + "' was not found"); + return storage.count(clusterId); + } + + /** + * {@inheritDoc} + */ + public OMetadataDefault getMetadata() { + checkOpeness(); + return metadata; + } + + /** + * {@inheritDoc} + */ + public DB checkSecurity(final ORule.ResourceGeneric resourceGeneric, final String resourceSpecific, + final int iOperation) { + if (user != null) { + try { + user.allow(resourceGeneric, resourceSpecific, iOperation); + } catch (OSecurityAccessException e) { + + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance() + .debug(this, "User '%s' tried to access the reserved resource '%s.%s', operation '%s'", getUser(), resourceGeneric, + resourceSpecific, iOperation); + + throw e; + } + } + return (DB) this; + } + + /** + * {@inheritDoc} + */ + public DB checkSecurity(final ORule.ResourceGeneric iResourceGeneric, final int iOperation, + final Object... iResourcesSpecific) { + + if (user != null) { + try { + if (iResourcesSpecific.length != 0) { + for (Object target : iResourcesSpecific) { + if (target != null) { + user.allow(iResourceGeneric, target.toString(), iOperation); + } else + user.allow(iResourceGeneric, null, iOperation); + } + } else + user.allow(iResourceGeneric, null, iOperation); + } catch (OSecurityAccessException e) { + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance() + .debug(this, "[checkSecurity] User '%s' tried to access the reserved resource '%s', target(s) '%s', operation '%s'", + getUser(), iResourceGeneric, Arrays.toString(iResourcesSpecific), iOperation); + + throw e; + } + } + return (DB) this; + } + + /** + * {@inheritDoc} + */ + public DB checkSecurity(final ORule.ResourceGeneric iResourceGeneric, final int iOperation, + final Object iResourceSpecific) { + checkOpeness(); + if (user != null) { + try { + if (iResourceSpecific != null) + user.allow(iResourceGeneric, iResourceSpecific.toString(), iOperation); + else + user.allow(iResourceGeneric, null, iOperation); + } catch (OSecurityAccessException e) { + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance() + .debug(this, "[checkSecurity] User '%s' tried to access the reserved resource '%s', target '%s', operation '%s'", + getUser(), iResourceGeneric, iResourceSpecific, iOperation); + + throw e; + } + } + return (DB) this; + } + + /** + * {@inheritDoc} + */ + @Override + public ODatabaseInternal getDatabaseOwner() { + ODatabaseInternal current = databaseOwner; + + while (current != null && current != this && current.getDatabaseOwner() != current) + current = current.getDatabaseOwner(); + + return current; + } + + /** + * {@inheritDoc} + */ + @Override + public ODatabaseInternal setDatabaseOwner(ODatabaseInternal iOwner) { + databaseOwner = iOwner; + return this; + } + + /** + * {@inheritDoc} + */ + public boolean isRetainRecords() { + return retainRecords; + } + + /** + * {@inheritDoc} + */ + public ODatabaseDocument setRetainRecords(boolean retainRecords) { + this.retainRecords = retainRecords; + return this; + } + + /** + * {@inheritDoc} + */ + public DB setStatus(final STATUS status) { + checkIfActive(); + setStatusInternal(status); + return (DB) this; + } + + public void setStatusInternal(final STATUS status) { + this.status = status; + } + + /** + * Deprecated since v2.2 + */ + @Deprecated + public void setDefaultClusterIdInternal(final int iDefClusterId) { + checkIfActive(); + getStorage().setDefaultClusterId(iDefClusterId); + } + + /** + * {@inheritDoc} + */ + public void setInternal(final ATTRIBUTES iAttribute, final Object iValue) { + set(iAttribute, iValue); + } + + /** + * {@inheritDoc} + */ + public OSecurityUser getUser() { + return user; + } + + /** + * {@inheritDoc} + */ + public void setUser(final OSecurityUser user) { + checkIfActive(); + if (user instanceof OUser) { + OMetadata metadata = getMetadata(); + if (metadata != null) { + final OSecurity security = metadata.getSecurity(); + this.user = new OImmutableUser(security.getVersion(), (OUser) user); + } else + this.user = new OImmutableUser(-1, (OUser) user); + } else + this.user = (OImmutableUser) user; + } + + public void reloadUser() { + if (user != null) { + activateOnCurrentThread(); + + OMetadata metadata = getMetadata(); + + if (metadata != null) { + final OSecurity security = metadata.getSecurity(); + OUser secGetUser = security.getUser(user.getName()); + + if (secGetUser != null) + user = new OImmutableUser(security.getVersion(), secGetUser); + else + user = new OImmutableUser(-1, new OUser()); + } else + user = new OImmutableUser(-1, new OUser()); + } + } + + /** + * {@inheritDoc} + */ + public boolean isMVCC() { + return mvcc; + } + + /** + * {@inheritDoc} + */ + public > DB setMVCC(boolean mvcc) { + this.mvcc = mvcc; + return (DB) this; + } + + /** + * {@inheritDoc} + */ + public ODictionary getDictionary() { + checkOpeness(); + return metadata.getIndexManager().getDictionary(); + } + + /** + * {@inheritDoc} + */ + public > DB registerHook(final ORecordHook iHookImpl, final ORecordHook.HOOK_POSITION iPosition) { + checkOpeness(); + checkIfActive(); + + final Map tmp = new LinkedHashMap(hooks); + tmp.put(iHookImpl, iPosition); + hooks.clear(); + for (ORecordHook.HOOK_POSITION p : ORecordHook.HOOK_POSITION.values()) { + for (Map.Entry e : tmp.entrySet()) { + if (e.getValue() == p) + hooks.put(e.getKey(), e.getValue()); + } + } + + compileHooks(); + + return (DB) this; + } + + /** + * {@inheritDoc} + */ + public > DB registerHook(final ORecordHook iHookImpl) { + return (DB) registerHook(iHookImpl, ORecordHook.HOOK_POSITION.REGULAR); + } + + /** + * {@inheritDoc} + */ + public > DB unregisterHook(final ORecordHook iHookImpl) { + checkIfActive(); + if (iHookImpl != null) { + iHookImpl.onUnregister(); + hooks.remove(iHookImpl); + + compileHooks(); + } + return (DB) this; + } + + /** + * {@inheritDoc} + */ + @Override + public OLocalRecordCache getLocalCache() { + return localCache; + } + + /** + * {@inheritDoc} + */ + public Map getHooks() { + return unmodifiableHooks; + } + + /** + * Callback the registered hooks if any. + * + * @param type Hook type. Define when hook is called. + * @param id Record received in the callback + * + * @return True if the input record is changed, otherwise false + */ + public ORecordHook.RESULT callbackHooks(final ORecordHook.TYPE type, final OIdentifiable id) { + if (id == null || hooks.isEmpty() || id.getIdentity().getClusterId() == 0) + return ORecordHook.RESULT.RECORD_NOT_CHANGED; + + final ORecordHook.SCOPE scope = ORecordHook.SCOPE.typeToScope(type); + final int scopeOrdinal = scope.ordinal(); + + ORID identity = id.getIdentity().copy(); + if (!pushInHook(identity)) + return ORecordHook.RESULT.RECORD_NOT_CHANGED; + + try { + final ORecord rec = id.getRecord(); + if (rec == null) + return ORecordHook.RESULT.RECORD_NOT_CHANGED; + + final OScenarioThreadLocal.RUN_MODE runMode = OScenarioThreadLocal.INSTANCE.getRunMode(); + + boolean recordChanged = false; + for (ORecordHook hook : hooksByScope[scopeOrdinal]) { + switch (runMode) { + case DEFAULT: // NON_DISTRIBUTED OR PROXIED DB + if (getStorage().isDistributed() + && hook.getDistributedExecutionMode() == ORecordHook.DISTRIBUTED_EXECUTION_MODE.TARGET_NODE) + // SKIP + continue; + break; // TARGET NODE + case RUNNING_DISTRIBUTED: + if (hook.getDistributedExecutionMode() == ORecordHook.DISTRIBUTED_EXECUTION_MODE.SOURCE_NODE) + continue; + } + + final ORecordHook.RESULT res = hook.onTrigger(type, rec); + + if (res == ORecordHook.RESULT.RECORD_CHANGED) + recordChanged = true; + else if (res == ORecordHook.RESULT.SKIP_IO) + // SKIP IO OPERATION + return res; + else if (res == ORecordHook.RESULT.SKIP) + // SKIP NEXT HOOKS AND RETURN IT + return res; + else if (res == ORecordHook.RESULT.RECORD_REPLACED) + return res; + } + + return recordChanged ? ORecordHook.RESULT.RECORD_CHANGED : ORecordHook.RESULT.RECORD_NOT_CHANGED; + + } finally { + popInHook(identity); + } + } + + /** + * {@inheritDoc} + */ + public boolean isValidationEnabled() { + return (Boolean) get(ATTRIBUTES.VALIDATION); + } + + /** + * {@inheritDoc} + */ + public DB setValidationEnabled(final boolean iEnabled) { + set(ATTRIBUTES.VALIDATION, iEnabled); + return (DB) this; + } + + public ORecordConflictStrategy getConflictStrategy() { + checkIfActive(); + return getStorage().getConflictStrategy(); + } + + public ODatabaseDocumentTx setConflictStrategy(final String iStrategyName) { + checkIfActive(); + getStorage().setConflictStrategy(Orient.instance().getRecordConflictStrategy().getStrategy(iStrategyName)); + return this; + } + + public ODatabaseDocumentTx setConflictStrategy(final ORecordConflictStrategy iResolver) { + checkIfActive(); + getStorage().setConflictStrategy(iResolver); + return this; + } + + @Override + public OContextConfiguration getConfiguration() { + checkIfActive(); + if (storage != null) + return storage.getConfiguration().getContextConfiguration(); + return null; + } + + @Override + public boolean declareIntent(final OIntent iIntent) { + checkIfActive(); + + if (currentIntent != null) { + if (iIntent != null && iIntent.getClass().equals(currentIntent.getClass())) + // SAME INTENT: JUMP IT + return false; + + // END CURRENT INTENT + currentIntent.end(this); + } + + currentIntent = iIntent; + + if (iIntent != null) + iIntent.begin(this); + + return true; + } + + @Override + public boolean exists() { + if (status == STATUS.OPEN) + return true; + + if (storage == null) + storage = Orient.instance().loadStorage(url); + + return storage.exists(); + } + + @Override + public void close() { + checkIfActive(); + + try { + localCache.shutdown(); + + if (isClosed()) { + status = STATUS.CLOSED; + return; + } + + try { + commit(true); + } catch (Exception e) { + OLogManager.instance().error(this, "Exception during commit of active transaction", e); + } + + if (status != STATUS.OPEN) + return; + + callOnCloseListeners(); + + if (currentIntent != null) { + currentIntent.end(this); + currentIntent = null; + } + + status = STATUS.CLOSED; + + localCache.clear(); + + if (!keepStorageOpen && storage != null) + storage.close(); + + } finally { + // ALWAYS RESET TL + ODatabaseRecordThreadLocal.INSTANCE.remove(); + clearOwner(); + } + } + + private void clearOwner() { + owner.set(null); + } + + @Override + public STATUS getStatus() { + return status; + } + + @Override + public long getSize() { + checkIfActive(); + return storage.getSize(); + } + + @Override + public String getName() { + return storage != null ? storage.getName() : url; + } + + @Override + public String getURL() { + return url != null ? url : storage.getURL(); + } + + @Override + public int getDefaultClusterId() { + checkIfActive(); + return storage.getDefaultClusterId(); + } + + @Override + public int getClusters() { + checkIfActive(); + return storage.getClusters(); + } + + @Override + public boolean existsCluster(final String iClusterName) { + checkIfActive(); + return storage.getClusterNames().contains(iClusterName.toLowerCase(Locale.ENGLISH)); + } + + @Override + public Collection getClusterNames() { + checkIfActive(); + return storage.getClusterNames(); + } + + @Override + public int getClusterIdByName(final String iClusterName) { + if (iClusterName == null) + return -1; + + checkIfActive(); + return storage.getClusterIdByName(iClusterName.toLowerCase(Locale.ENGLISH)); + } + + @Override + public String getClusterNameById(final int iClusterId) { + if (iClusterId < 0) + return null; + + checkIfActive(); + return storage.getPhysicalClusterNameById(iClusterId); + } + + @Override + public long getClusterRecordSizeByName(final String clusterName) { + checkIfActive(); + try { + return storage.getClusterById(getClusterIdByName(clusterName)).getRecordsSize(); + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Error on reading records size for cluster '" + clusterName + "'"), e); + } + } + + @Override + public long getClusterRecordSizeById(final int clusterId) { + checkIfActive(); + try { + return storage.getClusterById(clusterId).getRecordsSize(); + } catch (Exception e) { + throw OException + .wrapException(new ODatabaseException("Error on reading records size for cluster with id '" + clusterId + "'"), e); + } + } + + @Override + public boolean isClosed() { + return status == STATUS.CLOSED || storage.isClosed(); + } + + @Override + public int addCluster(final String iClusterName, final Object... iParameters) { + checkIfActive(); + + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_CREATE); + + return storage.addCluster(iClusterName, false, iParameters); + } + + @Override + public int addCluster(final String iClusterName, final int iRequestedId, final Object... iParameters) { + checkIfActive(); + + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_CREATE); + + return storage.addCluster(iClusterName, iRequestedId, false, iParameters); + } + + @Override + public Object alterCluster(String iClusterName, OCluster.ATTRIBUTES attribute, Object value) { + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + OCluster cluster = storage.getClusterById(storage.getClusterIdByName(iClusterName)); + + if (cluster == null) + throw new ODatabaseException("Cannot alter cluster with name: " + iClusterName); + + Object result; + + try { + if (attribute == OCluster.ATTRIBUTES.STATUS && OStorageClusterConfiguration.STATUS.OFFLINE.toString() + .equalsIgnoreCase((String) value)) + // REMOVE CACHE OF COMMAND RESULTS IF ACTIVE + getMetadata().getCommandCache().invalidateResultsOfCluster(iClusterName); + + if (attribute == OCluster.ATTRIBUTES.NAME) + // REMOVE CACHE OF COMMAND RESULTS IF ACTIVE + getMetadata().getCommandCache().invalidateResultsOfCluster(iClusterName); + + result = cluster.set(attribute, value); + } catch (Exception ex) { + throw OException.wrapException(new ODatabaseException("Error while altering cluster with name: " + iClusterName), ex); + } + + return result; + } + + @Override + public Object alterCluster(int iClusterId, OCluster.ATTRIBUTES attribute, Object value) { + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + OCluster cluster = storage.getClusterById(iClusterId); + + if (cluster == null) + throw new ODatabaseException("Cannot alter cluster with id: " + iClusterId); + + Object result; + + try { + if (attribute == OCluster.ATTRIBUTES.STATUS && OStorageClusterConfiguration.STATUS.OFFLINE.toString() + .equalsIgnoreCase((String) value)) + // REMOVE CACHE OF COMMAND RESULTS IF ACTIVE + getMetadata().getCommandCache().invalidateResultsOfCluster(getClusterNameById(iClusterId)); + + if (attribute == OCluster.ATTRIBUTES.NAME) + // REMOVE CACHE OF COMMAND RESULTS IF ACTIVE + getMetadata().getCommandCache().invalidateResultsOfCluster(getClusterNameById(iClusterId)); + + result = cluster.set(attribute, value); + } catch (Exception ex) { + throw OException.wrapException(new ODatabaseException("Error while altering cluster with id: " + iClusterId), ex); + } + + return result; + } + + @Override + public boolean dropCluster(final String iClusterName, final boolean iTruncate) { + checkIfActive(); + + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + final int clusterId = getClusterIdByName(iClusterName); + OSchemaProxy schema = metadata.getSchema(); + OClass clazz = schema.getClassByClusterId(clusterId); + if (clazz != null) + clazz.removeClusterId(clusterId); + if (schema.getBlobClusters().contains(clusterId)) + schema.removeBlobCluster(iClusterName); + getLocalCache().freeCluster(clusterId); + storage.checkForClusterPermissions(iClusterName); + return storage.dropCluster(iClusterName, iTruncate); + } + + @Override + public boolean dropCluster(final int iClusterId, final boolean iTruncate) { + checkIfActive(); + + checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + OSchemaProxy schema = metadata.getSchema(); + final OClass clazz = schema.getClassByClusterId(iClusterId); + if (clazz != null) + clazz.removeClusterId(iClusterId); + getLocalCache().freeCluster(iClusterId); + if (schema.getBlobClusters().contains(iClusterId)) + schema.removeBlobCluster(getClusterNameById(iClusterId)); + + storage.checkForClusterPermissions(getClusterNameById(iClusterId)); + return storage.dropCluster(iClusterId, iTruncate); + } + + @Override + public Object setProperty(final String iName, final Object iValue) { + if (iValue == null) + return properties.remove(iName.toLowerCase(Locale.ENGLISH)); + else + return properties.put(iName.toLowerCase(Locale.ENGLISH), iValue); + } + + @Override + public Object getProperty(final String iName) { + return properties.get(iName.toLowerCase(Locale.ENGLISH)); + } + + @Override + public Iterator> getProperties() { + return properties.entrySet().iterator(); + } + + @Override + public Object get(final ATTRIBUTES iAttribute) { + checkIfActive(); + + if (iAttribute == null) + throw new IllegalArgumentException("attribute is null"); + + switch (iAttribute) { + case STATUS: + return getStatus(); + case DEFAULTCLUSTERID: + return getDefaultClusterId(); + case TYPE: + return getMetadata().getImmutableSchemaSnapshot().existsClass("V") ? "graph" : "document"; + case DATEFORMAT: + return storage.getConfiguration().dateFormat; + + case DATETIMEFORMAT: + return storage.getConfiguration().dateTimeFormat; + + case TIMEZONE: + return storage.getConfiguration().getTimeZone().getID(); + + case LOCALECOUNTRY: + return storage.getConfiguration().getLocaleCountry(); + + case LOCALELANGUAGE: + return storage.getConfiguration().getLocaleLanguage(); + + case CHARSET: + return storage.getConfiguration().getCharset(); + + case CUSTOM: + return storage.getConfiguration().getProperties(); + + case CLUSTERSELECTION: + return storage.getConfiguration().getClusterSelection(); + + case MINIMUMCLUSTERS: + return storage.getConfiguration().getMinimumClusters(); + + case CONFLICTSTRATEGY: + return storage.getConfiguration().getConflictStrategy(); + + case VALIDATION: + return storage.getConfiguration().isValidationEnabled(); + } + + return null; + } + + @Override + public DB set(final ATTRIBUTES iAttribute, final Object iValue) { + checkIfActive(); + + if (iAttribute == null) + throw new IllegalArgumentException("attribute is null"); + + final String stringValue = OIOUtils.getStringContent(iValue != null ? iValue.toString() : null); + + switch (iAttribute) { + case STATUS: + if (stringValue == null) + throw new IllegalArgumentException("DB status can't be null"); + setStatus(STATUS.valueOf(stringValue.toUpperCase(Locale.ENGLISH))); + break; + + case DEFAULTCLUSTERID: + if (iValue != null) { + if (iValue instanceof Number) + storage.setDefaultClusterId(((Number) iValue).intValue()); + else + storage.setDefaultClusterId(storage.getClusterIdByName(iValue.toString())); + } + break; + + case TYPE: + throw new IllegalArgumentException("Database type cannot be changed at run-time"); + + case DATEFORMAT: + if (stringValue == null) + throw new IllegalArgumentException("date format is null"); + + // CHECK FORMAT + new SimpleDateFormat(stringValue).format(new Date()); + + storage.getConfiguration().dateFormat = stringValue; + storage.getConfiguration().update(); + break; + + case DATETIMEFORMAT: + if (stringValue == null) + throw new IllegalArgumentException("date format is null"); + + // CHECK FORMAT + new SimpleDateFormat(stringValue).format(new Date()); + + storage.getConfiguration().dateTimeFormat = stringValue; + storage.getConfiguration().update(); + break; + + case TIMEZONE: + if (stringValue == null) + throw new IllegalArgumentException("Timezone can't be null"); + + // for backward compatibility, until 2.1.13 OrientDB accepted timezones in lowercase as well + TimeZone timeZoneValue = TimeZone.getTimeZone(stringValue.toUpperCase(Locale.ENGLISH)); + if (timeZoneValue.equals(TimeZone.getTimeZone("GMT"))) { + timeZoneValue = TimeZone.getTimeZone(stringValue); + } + + storage.getConfiguration().setTimeZone(timeZoneValue); + storage.getConfiguration().update(); + break; + + case LOCALECOUNTRY: + storage.getConfiguration().setLocaleCountry(stringValue); + storage.getConfiguration().update(); + break; + + case LOCALELANGUAGE: + storage.getConfiguration().setLocaleLanguage(stringValue); + storage.getConfiguration().update(); + break; + + case CHARSET: + storage.getConfiguration().setCharset(stringValue); + storage.getConfiguration().update(); + break; + + case CUSTOM: + int indx = stringValue != null ? stringValue.indexOf('=') : -1; + if (indx < 0) { + if ("clear".equalsIgnoreCase(stringValue)) { + clearCustomInternal(); + } else + throw new IllegalArgumentException("Syntax error: expected = or clear, instead found: " + iValue); + } else { + String customName = stringValue.substring(0, indx).trim(); + String customValue = stringValue.substring(indx + 1).trim(); + if (customValue.isEmpty()) + removeCustomInternal(customName); + else + setCustomInternal(customName, customValue); + } + break; + + case CLUSTERSELECTION: + storage.getConfiguration().setClusterSelection(stringValue); + storage.getConfiguration().update(); + break; + + case MINIMUMCLUSTERS: + if (iValue != null) { + if (iValue instanceof Number) + storage.getConfiguration().setMinimumClusters(((Number) iValue).intValue()); + else + storage.getConfiguration().setMinimumClusters(Integer.parseInt(stringValue)); + } else + // DEFAULT = 1 + storage.getConfiguration().setMinimumClusters(1); + + storage.getConfiguration().update(); + break; + + case CONFLICTSTRATEGY: + storage.setConflictStrategy(Orient.instance().getRecordConflictStrategy().getStrategy(stringValue)); + storage.getConfiguration().setConflictStrategy(stringValue); + storage.getConfiguration().update(); + break; + + case VALIDATION: + storage.getConfiguration().setValidation(Boolean.parseBoolean(stringValue)); + storage.getConfiguration().update(); + break; + + default: + throw new IllegalArgumentException("Option '" + iAttribute + "' not supported on alter database"); + + } + + return (DB) this; + } + + @Override + public ORecordMetadata getRecordMetadata(final ORID rid) { + checkIfActive(); + return storage.getRecordMetadata(rid); + } + + public OTransaction getTransaction() { + checkIfActive(); + return currentTx; + } + + @SuppressWarnings("unchecked") + @Override + public RET load(final ORecord iRecord, final String iFetchPlan) { + checkIfActive(); + return (RET) currentTx.loadRecord(iRecord.getIdentity(), iRecord, iFetchPlan, false, false, OStorage.LOCKING_STRATEGY.DEFAULT); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public RET load(ORecord iRecord, String iFetchPlan, boolean iIgnoreCache, boolean loadTombstone, + OStorage.LOCKING_STRATEGY iLockingStrategy) { + checkIfActive(); + return (RET) currentTx + .loadRecord(iRecord.getIdentity(), iRecord, iFetchPlan, iIgnoreCache, !iIgnoreCache, loadTombstone, iLockingStrategy); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public RET load(final ORecord iRecord, final String iFetchPlan, final boolean iIgnoreCache, + final boolean iUpdateCache, final boolean loadTombstone, final OStorage.LOCKING_STRATEGY iLockingStrategy) { + checkIfActive(); + return (RET) currentTx + .loadRecord(iRecord.getIdentity(), iRecord, iFetchPlan, iIgnoreCache, iUpdateCache, loadTombstone, iLockingStrategy); + } + + @SuppressWarnings("unchecked") + @Override + public RET load(final ORecord iRecord) { + checkIfActive(); + return (RET) currentTx.loadRecord(iRecord.getIdentity(), iRecord, null, false); + } + + @SuppressWarnings("unchecked") + @Override + public RET load(final ORID recordId) { + return (RET) currentTx.loadRecord(recordId, null, null, false); + } + + @SuppressWarnings("unchecked") + @Override + public RET load(final ORID iRecordId, final String iFetchPlan) { + checkIfActive(); + return (RET) currentTx.loadRecord(iRecordId, null, iFetchPlan, false); + } + + @SuppressWarnings("unchecked") + public RET loadIfVersionIsNotLatest(final ORID rid, final int recordVersion, String fetchPlan, + boolean ignoreCache) throws ORecordNotFoundException { + checkIfActive(); + return (RET) currentTx.loadRecordIfVersionIsNotLatest(rid, recordVersion, fetchPlan, ignoreCache); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public RET load(final ORID iRecordId, String iFetchPlan, final boolean iIgnoreCache, + final boolean loadTombstone, OStorage.LOCKING_STRATEGY iLockingStrategy) { + checkIfActive(); + return (RET) currentTx.loadRecord(iRecordId, null, iFetchPlan, iIgnoreCache, loadTombstone, iLockingStrategy); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public RET load(final ORID iRecordId, String iFetchPlan, final boolean iIgnoreCache, + final boolean iUpdateCache, final boolean loadTombstone, OStorage.LOCKING_STRATEGY iLockingStrategy) { + checkIfActive(); + return (RET) currentTx.loadRecord(iRecordId, null, iFetchPlan, iIgnoreCache, iUpdateCache, loadTombstone, iLockingStrategy); + } + + @SuppressWarnings("unchecked") + public RET reload(final ORecord iRecord) { + return reload(iRecord, null, false); + } + + @SuppressWarnings("unchecked") + public RET reload(final ORecord iRecord, final String iFetchPlan) { + return reload(iRecord, iFetchPlan, false); + } + + @SuppressWarnings("unchecked") + @Override + public RET reload(final ORecord iRecord, final String iFetchPlan, final boolean iIgnoreCache) { + return reload(iRecord, iFetchPlan, iIgnoreCache, true); + } + + @Override + public RET reload(final ORecord record, String fetchPlan, boolean ignoreCache, boolean force) { + checkIfActive(); + + final ORecord loadedRecord = currentTx.reloadRecord(record.getIdentity(), record, fetchPlan, ignoreCache, force); + + if (loadedRecord != null && record != loadedRecord) { + record.fromStream(loadedRecord.toStream()); + ORecordInternal.setVersion(record, loadedRecord.getVersion()); + } else if (loadedRecord == null) { + throw new ORecordNotFoundException(record.getIdentity()); + } + + return (RET) record; + } + + /** + * Deletes the record without checking the version. + */ + public ODatabaseDocument delete(final ORID iRecord) { + checkOpeness(); + checkIfActive(); + + final ORecord rec = iRecord.getRecord(); + if (rec != null) + rec.delete(); + return this; + } + + @Override + public void recycle(final ORecord record) { + checkOpeness(); + if (record == null) + throw new ODatabaseException("Cannot recycle null document"); + + // CHECK ACCESS ON SCHEMA CLASS NAME (IF ANY) + if (record instanceof ODocument && ((ODocument) record).getClassName() != null) + checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_CREATE, ((ODocument) record).getClassName()); + + try { + currentTx.recycleRecord(record); + } catch (OException e) { + throw e; + } catch (Exception e) { + if (record instanceof ODocument) + throw OException.wrapException(new ODatabaseException( + "Error on recycling record " + record.getIdentity() + " of class '" + ((ODocument) record).getClassName() + "'"), e); + else + throw OException.wrapException(new ODatabaseException("Error on recycling record " + record.getIdentity()), e); + } + } + + @Override + public boolean hide(ORID rid) { + checkOpeness(); + checkIfActive(); + + if (currentTx.isActive()) + throw new ODatabaseException("This operation can be executed only in non transaction mode"); + + return executeHideRecord(rid, OPERATION_MODE.SYNCHRONOUS); + } + + @Override + public OBinarySerializerFactory getSerializerFactory() { + return componentsFactory.binarySerializerFactory; + } + + public ODatabaseDocument begin(final OTransaction iTx) { + checkOpeness(); + checkIfActive(); + + if (currentTx.isActive() && iTx.equals(currentTx)) { + currentTx.begin(); + return this; + } + + currentTx.rollback(true, 0); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onBeforeTxBegin(this); + } catch (Exception e) { + final String message = "Error before the transaction begin"; + + OLogManager.instance().error(this, message, e); + throw OException.wrapException(new OTransactionBlockedException(message), e); + } + + currentTx = iTx; + currentTx.begin(); + + return this; + } + + /** + * {@inheritDoc} + */ + public RET load(final ORecord iRecord, final String iFetchPlan, final boolean iIgnoreCache) { + return (RET) executeReadRecord((ORecordId) iRecord.getIdentity(), iRecord, -1, iFetchPlan, iIgnoreCache, !iIgnoreCache, false, + OStorage.LOCKING_STRATEGY.NONE, new SimpleRecordReader(prefetchRecords)); + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public Set executeReadRecords(final Set iRids, final boolean ignoreCache) { + checkOpeness(); + checkIfActive(); + + getMetadata().makeThreadLocalSchemaSnapshot(); + ORecordSerializationContext.pushContext(); + try { + + final Set records = new HashSet(iRids.size() > 0 ? iRids.size() : 1); + + if (iRids.isEmpty()) + return records; + + final Collection rids = new ArrayList(iRids); + + for (Iterator it = rids.iterator(); it.hasNext(); ) { + final ORecordId rid = it.next(); + + // SEARCH IN LOCAL TX + ORecord record = getTransaction().getRecord(rid); + if (record == OTransactionRealAbstract.DELETED_RECORD) { + // DELETED IN TX + it.remove(); + continue; + } + + if (record == null && !ignoreCache) + // SEARCH INTO THE CACHE + record = getLocalCache().findRecord(rid); + + if (record != null) { + // FOUND FROM CACHE + records.add(record); + it.remove(); + } + } + + final Collection> rawRecords = ((OAbstractPaginatedStorage) storage.getUnderlying()) + .readRecords(rids); + for (OPair entry : rawRecords) { + // NO SAME RECORD TYPE: CAN'T REUSE OLD ONE BUT CREATE A NEW ONE FOR IT + final ORecord record = Orient.instance().getRecordFactoryManager().newInstance(entry.value.recordType); + ORecordInternal.fill(record, entry.key, entry.value.version, entry.value.buffer, false); + records.add(record); + } + + return records; + + } finally { + ORecordSerializationContext.pullContext(); + getMetadata().clearThreadLocalSchemaSnapshot(); + } + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public RET executeReadRecord(final ORecordId rid, ORecord iRecord, final int recordVersion, + final String fetchPlan, final boolean ignoreCache, final boolean iUpdateCache, final boolean loadTombstones, + final OStorage.LOCKING_STRATEGY lockingStrategy, RecordReader recordReader) { + checkOpeness(); + checkIfActive(); + + getMetadata().makeThreadLocalSchemaSnapshot(); + ORecordSerializationContext.pushContext(); + try { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, getClusterNameById(rid.getClusterId())); + + // SEARCH IN LOCAL TX + ORecord record = getTransaction().getRecord(rid); + if (record == OTransactionRealAbstract.DELETED_RECORD) + // DELETED IN TX + return null; + + if (record == null && !ignoreCache) + // SEARCH INTO THE CACHE + record = getLocalCache().findRecord(rid); + + if (record != null) { + if (iRecord != null) { + iRecord.fromStream(record.toStream()); + ORecordInternal.setVersion(iRecord, record.getVersion()); + record = iRecord; + } + + OFetchHelper.checkFetchPlanValid(fetchPlan); + if (callbackHooks(ORecordHook.TYPE.BEFORE_READ, record) == ORecordHook.RESULT.SKIP) + return null; + + if (record.getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) + record.reload(); + + if (lockingStrategy == OStorage.LOCKING_STRATEGY.KEEP_SHARED_LOCK) { + OLogManager.instance() + .warn(this, "You use deprecated record locking strategy: %s it may lead to deadlocks " + lockingStrategy); + record.lock(false); + + } else if (lockingStrategy == OStorage.LOCKING_STRATEGY.KEEP_EXCLUSIVE_LOCK) { + OLogManager.instance() + .warn(this, "You use deprecated record locking strategy: %s it may lead to deadlocks " + lockingStrategy); + record.lock(true); + } + + callbackHooks(ORecordHook.TYPE.AFTER_READ, record); + if (record instanceof ODocument) + ODocumentInternal.checkClass((ODocument) record, this); + return (RET) record; + } + + final ORawBuffer recordBuffer; + if (!rid.isValid()) + recordBuffer = null; + else { + OFetchHelper.checkFetchPlanValid(fetchPlan); + + int version; + if (iRecord != null) + version = iRecord.getVersion(); + else + version = recordVersion; + + recordBuffer = recordReader.readRecord(storage, rid, fetchPlan, ignoreCache, version); + } + + if (recordBuffer == null) + return null; + + if (iRecord == null || ORecordInternal.getRecordType(iRecord) != recordBuffer.recordType) + // NO SAME RECORD TYPE: CAN'T REUSE OLD ONE BUT CREATE A NEW ONE FOR IT + iRecord = Orient.instance().getRecordFactoryManager().newInstance(recordBuffer.recordType); + + ORecordInternal.fill(iRecord, rid, recordBuffer.version, recordBuffer.buffer, false); + + if (iRecord instanceof ODocument) + ODocumentInternal.checkClass((ODocument) iRecord, this); + + if (ORecordVersionHelper.isTombstone(iRecord.getVersion())) + return (RET) iRecord; + + if (callbackHooks(ORecordHook.TYPE.BEFORE_READ, iRecord) == ORecordHook.RESULT.SKIP) + return null; + + iRecord.fromStream(recordBuffer.buffer); + + callbackHooks(ORecordHook.TYPE.AFTER_READ, iRecord); + + if (iUpdateCache) + getLocalCache().updateRecord(iRecord); + + return (RET) iRecord; + } catch (OOfflineClusterException t) { + throw t; + } catch (ORecordNotFoundException t) { + throw t; + } catch (Throwable t) { + if (rid.isTemporary()) + throw OException.wrapException(new ODatabaseException("Error on retrieving record using temporary RID: " + rid), t); + else + throw OException.wrapException(new ODatabaseException( + "Error on retrieving record " + rid + " (cluster: " + storage.getPhysicalClusterNameById(rid.getClusterId()) + ")"), t); + } finally { + ORecordSerializationContext.pullContext(); + getMetadata().clearThreadLocalSchemaSnapshot(); + } + } + + public int assignAndCheckCluster(final ORecord record, final String iClusterName) { + final ORecordId rid = (ORecordId) record.getIdentity(); + // if provided a cluster name use it. + if (rid.getClusterId() <= ORID.CLUSTER_POS_INVALID && iClusterName != null) { + rid.setClusterId(getClusterIdByName(iClusterName)); + if (rid.getClusterId() == -1) + throw new IllegalArgumentException("Cluster name '" + iClusterName + "' is not configured"); + + } + OClass schemaClass = null; + // if cluster id is not set yet try to find it out + if (rid.getClusterId() <= ORID.CLUSTER_ID_INVALID && storage.isAssigningClusterIds()) { + if (record instanceof ODocument) { + schemaClass = ODocumentInternal.getImmutableSchemaClass(((ODocument) record)); + if (schemaClass != null) { + if (schemaClass.isAbstract()) + throw new OSchemaException("Document belongs to abstract class " + schemaClass.getName() + " and cannot be saved"); + rid.setClusterId(schemaClass.getClusterForNewInstance((ODocument) record)); + } else + rid.setClusterId(getDefaultClusterId()); + } else { + rid.setClusterId(getDefaultClusterId()); + if (record instanceof OBlob && rid.getClusterId() != ORID.CLUSTER_ID_INVALID) { + // Set blobClusters = getMetadata().getSchema().getBlobClusters(); + // if (!blobClusters.contains(rid.clusterId) && rid.clusterId != getDefaultClusterId() && rid.clusterId != 0) { + // if (iClusterName == null) + // iClusterName = getClusterNameById(rid.clusterId); + // throw new IllegalArgumentException( + // "Cluster name '" + iClusterName + "' (id=" + rid.clusterId + ") is not configured to store blobs, valid are " + // + blobClusters.toString()); + // } + } + } + } else if (record instanceof ODocument) + schemaClass = ODocumentInternal.getImmutableSchemaClass(((ODocument) record)); + // If the cluster id was set check is validity + if (rid.getClusterId() > ORID.CLUSTER_ID_INVALID) { + if (schemaClass != null) { + String messageClusterName = getClusterNameById(rid.getClusterId()); + checkRecordClass(schemaClass, messageClusterName, rid); + if (!schemaClass.hasClusterId(rid.getClusterId())) { + // BYPASS THE IMMUTABLE CLASS + final OClass dbClass = metadata.getSchema().getClass(schemaClass.getName()); + + if (!dbClass.hasClusterId(rid.getClusterId())) { + throw new IllegalArgumentException( + "Cluster name '" + messageClusterName + "' (id=" + rid.getClusterId() + ") is not configured to store the class '" + + schemaClass.getName() + "', valid are " + Arrays.toString(schemaClass.getClusterIds())); + } + } + } + } + return rid.getClusterId(); + } + + public RET executeSaveEmptyRecord(ORecord record, String clusterName) { + ORecordId rid = (ORecordId) record.getIdentity(); + assert rid.isNew(); + + ORecordInternal.onBeforeIdentityChanged(record); + int id = assignAndCheckCluster(record, clusterName); + clusterName = getClusterNameById(id); + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_CREATE, clusterName); + + byte[] content = getSerializer().writeClassOnly(record); + + final OStorageOperationResult ppos = storage + .createRecord(rid, content, record.getVersion(), recordType, OPERATION_MODE.SYNCHRONOUS.ordinal(), null); + + ORecordInternal.setVersion(record, ppos.getResult().recordVersion); + ((ORecordId) record.getIdentity()).copyFrom(rid); + ORecordInternal.onAfterIdentityChanged(record); + + return (RET) record; + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public RET executeSaveRecord(final ORecord record, String clusterName, final int ver, + final OPERATION_MODE mode, boolean forceCreate, final ORecordCallback recordCreatedCallback, + ORecordCallback recordUpdatedCallback) { + checkOpeness(); + checkIfActive(); + if (!record.isDirty()) + return (RET) record; + + final ORecordId rid = (ORecordId) record.getIdentity(); + + if (rid == null) + throw new ODatabaseException( + "Cannot create record because it has no identity. Probably is not a regular record or contains projections of fields rather than a full record"); + + record.setInternalStatus(ORecordElement.STATUS.MARSHALLING); + try { + + byte[] stream = null; + final OStorageOperationResult operationResult; + + getMetadata().makeThreadLocalSchemaSnapshot(); + if (record instanceof ODocument) + ODocumentInternal.checkClass((ODocument) record, this); + ORecordSerializationContext.pushContext(); + final boolean isNew = forceCreate || rid.isNew(); + try { + + final ORecordHook.TYPE triggerType; + if (isNew) { + // NOTIFY IDENTITY HAS CHANGED + ORecordInternal.onBeforeIdentityChanged(record); + int id = assignAndCheckCluster(record, clusterName); + clusterName = getClusterNameById(id); + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_CREATE, clusterName); + triggerType = ORecordHook.TYPE.BEFORE_CREATE; + } else { + clusterName = getClusterNameById(record.getIdentity().getClusterId()); + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_UPDATE, clusterName); + triggerType = ORecordHook.TYPE.BEFORE_UPDATE; + } + stream = record.toStream(); + + final ORecordHook.RESULT hookResult = callbackHooks(triggerType, record); + + if (hookResult == ORecordHook.RESULT.RECORD_CHANGED) { + if (record instanceof ODocument) + ((ODocument) record).validate(); + stream = updateStream(record); + } else if (hookResult == ORecordHook.RESULT.SKIP_IO) + return (RET) record; + else if (hookResult == ORecordHook.RESULT.RECORD_REPLACED) + // RETURNED THE REPLACED RECORD + return (RET) OHookReplacedRecordThreadLocal.INSTANCE.get(); + + ORecordSaveThreadLocal.setLast(record); + try { + // SAVE IT + boolean updateContent = ORecordInternal.isContentChanged(record); + byte[] content = (stream == null) ? OCommonConst.EMPTY_BYTE_ARRAY : stream; + byte recordType = ORecordInternal.getRecordType(record); + final int modeIndex = mode.ordinal(); + + // CHECK IF RECORD TYPE IS SUPPORTED + Orient.instance().getRecordFactoryManager().getRecordTypeClass(recordType); + + if (forceCreate || ORecordId.isNew(rid.getClusterPosition())) { + // CREATE + final OStorageOperationResult ppos = storage + .createRecord(rid, content, ver, recordType, modeIndex, (ORecordCallback) recordCreatedCallback); + operationResult = new OStorageOperationResult(ppos.getResult().recordVersion, ppos.isMoved()); + + } else { + // UPDATE + operationResult = storage.updateRecord(rid, updateContent, content, ver, recordType, modeIndex, recordUpdatedCallback); + } + + final int version = operationResult.getResult(); + + if (isNew) { + // UPDATE INFORMATION: CLUSTER ID+POSITION + ((ORecordId) record.getIdentity()).copyFrom(rid); + // NOTIFY IDENTITY HAS CHANGED + ORecordInternal.onAfterIdentityChanged(record); + // UPDATE INFORMATION: CLUSTER ID+POSITION + } + + if (operationResult.getModifiedRecordContent() != null) + stream = operationResult.getModifiedRecordContent(); + else if (version > record.getVersion() + 1 && storage instanceof OStorageProxy) + // IN CASE OF REMOTE CONFLICT STRATEGY FORCE UNLOAD DUE TO INVALID CONTENT + record.unload(); + + ORecordInternal.fill(record, rid, version, stream, false); + + callbackHookSuccess(record, isNew, stream, operationResult); + } catch (Exception t) { + callbackHookFailure(record, isNew, stream); + throw t; + } + } finally { + callbackHookFinalize(record, isNew, stream); + ORecordSerializationContext.pullContext(); + getMetadata().clearThreadLocalSchemaSnapshot(); + ORecordSaveThreadLocal.removeLast(); + } + + if (stream != null && stream.length > 0 && !operationResult.isMoved()) + // ADD/UPDATE IT IN CACHE IF IT'S ACTIVE + getLocalCache().updateRecord(record); + } catch (OException e) { + throw e; + } catch (Exception t) { + if (!ORecordId.isValid(record.getIdentity().getClusterPosition())) + throw OException + .wrapException(new ODatabaseException("Error on saving record in cluster #" + record.getIdentity().getClusterId()), t); + else + throw OException.wrapException(new ODatabaseException("Error on saving record " + record.getIdentity()), t); + + } finally { + record.setInternalStatus(ORecordElement.STATUS.LOADED); + } + return (RET) record; + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public void executeDeleteRecord(OIdentifiable record, final int iVersion, final boolean iRequired, final OPERATION_MODE iMode, + boolean prohibitTombstones) { + checkOpeness(); + checkIfActive(); + + final ORecordId rid = (ORecordId) record.getIdentity(); + + if (rid == null) + throw new ODatabaseException( + "Cannot delete record because it has no identity. Probably was created from scratch or contains projections of fields rather than a full record"); + + if (!rid.isValid()) + return; + + record = record.getRecord(); + if (record == null) + return; + + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_DELETE, getClusterNameById(rid.getClusterId())); + + ORecordSerializationContext.pushContext(); + getMetadata().makeThreadLocalSchemaSnapshot(); + try { + if (record instanceof ODocument) { + ODocumentInternal.checkClass((ODocument) record, this); + } + try { + // if cache is switched off record will be unreachable after delete. + ORecord rec = record.getRecord(); + if (rec != null) { + callbackHooks(ORecordHook.TYPE.BEFORE_DELETE, rec); + + if (rec instanceof ODocument) + ORidBagDeleter.deleteAllRidBags((ODocument) rec); + } + + final OStorageOperationResult operationResult; + try { + if (prohibitTombstones) { + final boolean result = storage.cleanOutRecord(rid, iVersion, iMode.ordinal(), null); + if (!result && iRequired) + throw new ORecordNotFoundException(rid); + operationResult = new OStorageOperationResult(result); + } else { + final OStorageOperationResult result = storage.deleteRecord(rid, iVersion, iMode.ordinal(), null); + if (!result.getResult() && iRequired) + throw new ORecordNotFoundException(rid); + operationResult = new OStorageOperationResult(result.getResult()); + } + + if (!operationResult.isMoved() && rec != null) + callbackHooks(ORecordHook.TYPE.AFTER_DELETE, rec); + else if (rec != null) + callbackHooks(ORecordHook.TYPE.DELETE_REPLICATED, rec); + } catch (Exception t) { + callbackHooks(ORecordHook.TYPE.DELETE_FAILED, rec); + throw t; + } finally { + callbackHooks(ORecordHook.TYPE.FINALIZE_DELETION, rec); + } + + clearDocumentTracking(rec); + + // REMOVE THE RECORD FROM 1 AND 2 LEVEL CACHES + if (!operationResult.isMoved()) { + getLocalCache().deleteRecord(rid); + } + + } catch (OException e) { + // RE-THROW THE EXCEPTION + throw e; + + } catch (Exception t) { + // WRAP IT AS ODATABASE EXCEPTION + throw OException + .wrapException(new ODatabaseException("Error on deleting record in cluster #" + record.getIdentity().getClusterId()), + t); + } + } finally { + ORecordSerializationContext.pullContext(); + getMetadata().clearThreadLocalSchemaSnapshot(); + } + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public void executeRecycleRecord(final ORecord record) { + checkOpeness(); + checkIfActive(); + + final ORecordId rid = (ORecordId) record.getIdentity(); + + if (rid == null) + throw new ODatabaseException( + "Cannot recycle record because it has no identity. Probably is not a regular record or contains projections of fields rather than a full record"); + + storage.recyclePosition(rid, record.toStream(), record.getVersion(), ODocument.RECORD_TYPE); + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + public boolean executeHideRecord(OIdentifiable record, final OPERATION_MODE iMode) { + checkOpeness(); + checkIfActive(); + + final ORecordId rid = (ORecordId) record.getIdentity(); + + if (rid == null) + throw new ODatabaseException( + "Cannot hide record because it has no identity. Probably was created from scratch or contains projections of fields rather than a full record"); + + if (!rid.isValid()) + return false; + + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_DELETE, getClusterNameById(rid.getClusterId())); + + getMetadata().makeThreadLocalSchemaSnapshot(); + if (record instanceof ODocument) + ODocumentInternal.checkClass((ODocument) record, this); + ORecordSerializationContext.pushContext(); + try { + + final OStorageOperationResult operationResult; + operationResult = storage.hideRecord(rid, iMode.ordinal(), null); + + // REMOVE THE RECORD FROM 1 AND 2 LEVEL CACHES + if (!operationResult.isMoved()) + getLocalCache().deleteRecord(rid); + + return operationResult.getResult(); + } finally { + ORecordSerializationContext.pullContext(); + getMetadata().clearThreadLocalSchemaSnapshot(); + } + } + + public ODatabaseDocumentTx begin() { + return begin(OTransaction.TXTYPE.OPTIMISTIC); + } + + public ODatabaseDocumentTx begin(final OTransaction.TXTYPE iType) { + checkOpeness(); + checkIfActive(); + + if (currentTx.isActive()) { + if (iType == OTransaction.TXTYPE.OPTIMISTIC && currentTx instanceof OTransactionOptimistic) { + currentTx.begin(); + return this; + } + + currentTx.rollback(true, 0); + } + + // CHECK IT'S NOT INSIDE A HOOK + if (!inHook.isEmpty()) + throw new IllegalStateException("Cannot begin a transaction while a hook is executing"); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onBeforeTxBegin(this); + } catch (Throwable t) { + OLogManager.instance().error(this, "Error before tx begin", t); + } + + switch (iType) { + case NOTX: + setDefaultTransactionMode(); + break; + + case OPTIMISTIC: + currentTx = new OTransactionOptimistic(this); + break; + + case PESSIMISTIC: + throw new UnsupportedOperationException("Pessimistic transaction"); + } + + currentTx.begin(); + return this; + } + + public void setDefaultTransactionMode() { + if (!(currentTx instanceof OTransactionNoTx)) + currentTx = new OTransactionNoTx(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void freeze(final boolean throwException) { + checkOpeness(); + if (!(getStorage() instanceof OFreezableStorageComponent)) { + OLogManager.instance().error(this, + "Only local paginated storage supports freeze. If you are using remote client please use OServerAdmin instead"); + + return; + } + + final long startTime = Orient.instance().getProfiler().startChrono(); + + final OFreezableStorageComponent storage = getFreezableStorage(); + if (storage != null) { + storage.freeze(throwException); + } + + Orient.instance().getProfiler() + .stopChrono("db." + getName() + ".freeze", "Time to freeze the database", startTime, "db.*.freeze"); + } + + @Override + public boolean isFrozen() { + checkOpeness(); + if (!(getStorage() instanceof OFreezableStorageComponent)) + return false; + + final OFreezableStorageComponent storage = getFreezableStorage(); + if (storage != null) + return storage.isFrozen(); + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void freeze() { + checkOpeness(); + if (!(getStorage() instanceof OFreezableStorageComponent)) { + OLogManager.instance().error(this, + "Only local paginated storage supports freeze. " + "If you use remote client please use OServerAdmin instead"); + + return; + } + + final long startTime = Orient.instance().getProfiler().startChrono(); + + final OFreezableStorageComponent storage = getFreezableStorage(); + if (storage != null) { + storage.freeze(false); + } + + Orient.instance().getProfiler() + .stopChrono("db." + getName() + ".freeze", "Time to freeze the database", startTime, "db.*.freeze"); + } + + /** + * {@inheritDoc} + */ + @Override + public void release() { + checkOpeness(); + if (!(getStorage() instanceof OFreezableStorageComponent)) { + OLogManager.instance().error(this, + "Only local paginated storage supports release. If you are using remote client please use OServerAdmin instead"); + return; + } + + final long startTime = Orient.instance().getProfiler().startChrono(); + + final OFreezableStorageComponent storage = getFreezableStorage(); + if (storage != null) { + storage.release(); + } + + Orient.instance().getProfiler() + .stopChrono("db." + getName() + ".release", "Time to release the database", startTime, "db.*.release"); + } + + /** + * Creates a new ODocument. + */ + public ODocument newInstance() { + return new ODocument(); + } + + /** + * Creates a document with specific class. + * + * @param iClassName the name of class that should be used as a class of created document. + * + * @return new instance of document. + */ + @Override + public ODocument newInstance(final String iClassName) { + return new ODocument(iClassName); + } + + /** + * {@inheritDoc} + */ + public ORecordIteratorClass browseClass(final String iClassName) { + return browseClass(iClassName, true); + } + + /** + * {@inheritDoc} + */ + public ORecordIteratorClass browseClass(final String iClassName, final boolean iPolymorphic) { + if (getMetadata().getImmutableSchemaSnapshot().getClass(iClassName) == null) + throw new IllegalArgumentException("Class '" + iClassName + "' not found in current database"); + + checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, iClassName); + return new ORecordIteratorClass(this, this, iClassName, iPolymorphic, false); + } + + /** + * {@inheritDoc} + */ + @Override + public ORecordIteratorCluster browseCluster(final String iClusterName) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + + return new ORecordIteratorCluster(this, this, getClusterIdByName(iClusterName)); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable getListeners() { + return getListenersCopy(); + } + + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public ORecordIteratorCluster browseCluster(String iClusterName, long startClusterPosition, long endClusterPosition, + boolean loadTombstones) { + checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, iClusterName); + + return new ORecordIteratorCluster(this, this, getClusterIdByName(iClusterName), startClusterPosition, + endClusterPosition, loadTombstones, OStorage.LOCKING_STRATEGY.DEFAULT); + } + + /** + * Saves a document to the database. Behavior depends by the current running transaction if any. If no transaction is running then + * changes apply immediately. If an Optimistic transaction is running then the record will be changed at commit time. The current + * transaction will continue to see the record as modified, while others not. If a Pessimistic transaction is running, then an + * exclusive lock is acquired against the record. Current transaction will continue to see the record as modified, while others + * cannot access to it since it's locked. + *

      + * If MVCC is enabled and the version of the document is different by the version stored in the database, then a + * {@link OConcurrentModificationException} exception is thrown.Before to save the document it must be valid following the + * constraints declared in the schema if any (can work also in schema-less mode). To validate the document the + * {@link ODocument#validate()} is called. + * + * @param iRecord Record to save. + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * + * @throws OConcurrentModificationException if the version of the document is different by the version contained in the database. + * @throws OValidationException if the document breaks some validation constraints defined in the schema + * @see #setMVCC(boolean), {@link #isMVCC()} + */ + @Override + public RET save(final ORecord iRecord) { + return (RET) save(iRecord, null, OPERATION_MODE.SYNCHRONOUS, false, null, null); + } + + /** + * Saves a document to the database. Behavior depends by the current running transaction if any. If no transaction is running then + * changes apply immediately. If an Optimistic transaction is running then the record will be changed at commit time. The current + * transaction will continue to see the record as modified, while others not. If a Pessimistic transaction is running, then an + * exclusive lock is acquired against the record. Current transaction will continue to see the record as modified, while others + * cannot access to it since it's locked. + *

      + * If MVCC is enabled and the version of the document is different by the version stored in the database, then a + * {@link OConcurrentModificationException} exception is thrown.Before to save the document it must be valid following the + * constraints declared in the schema if any (can work also in schema-less mode). To validate the document the + * {@link ODocument#validate()} is called. + * + * @param iRecord Record to save. + * @param iForceCreate Flag that indicates that record should be created. If record with current rid already exists, + * exception is thrown + * @param iRecordCreatedCallback callback that is called after creation of new record + * @param iRecordUpdatedCallback callback that is called after record update + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * + * @throws OConcurrentModificationException if the version of the document is different by the version contained in the database. + * @throws OValidationException if the document breaks some validation constraints defined in the schema + * @see #setMVCC(boolean), {@link #isMVCC()} + */ + @Override + public RET save(final ORecord iRecord, final OPERATION_MODE iMode, boolean iForceCreate, + final ORecordCallback iRecordCreatedCallback, ORecordCallback iRecordUpdatedCallback) { + return save(iRecord, null, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback); + } + + /** + * Saves a document specifying a cluster where to store the record. Behavior depends by the current running transaction if any. If + * no transaction is running then changes apply immediately. If an Optimistic transaction is running then the record will be + * changed at commit time. The current transaction will continue to see the record as modified, while others not. If a Pessimistic + * transaction is running, then an exclusive lock is acquired against the record. Current transaction will continue to see the + * record as modified, while others cannot access to it since it's locked. + *

      + * If MVCC is enabled and the version of the document is different by the version stored in the database, then a + * {@link OConcurrentModificationException} exception is thrown. Before to save the document it must be valid following the + * constraints declared in the schema if any (can work also in schema-less mode). To validate the document the + * {@link ODocument#validate()} is called. + * + * @param iRecord Record to save + * @param iClusterName Cluster name where to save the record + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * + * @throws OConcurrentModificationException if the version of the document is different by the version contained in the database. + * @throws OValidationException if the document breaks some validation constraints defined in the schema + * @see #setMVCC(boolean), {@link #isMVCC()}, ODocument#validate() + */ + @Override + public RET save(final ORecord iRecord, final String iClusterName) { + return (RET) save(iRecord, iClusterName, OPERATION_MODE.SYNCHRONOUS, false, null, null); + } + + /** + * Saves a document specifying a cluster where to store the record. Behavior depends by the current running transaction if any. If + * no transaction is running then changes apply immediately. If an Optimistic transaction is running then the record will be + * changed at commit time. The current transaction will continue to see the record as modified, while others not. If a Pessimistic + * transaction is running, then an exclusive lock is acquired against the record. Current transaction will continue to see the + * record as modified, while others cannot access to it since it's locked. + *

      + * If MVCC is enabled and the version of the document is different by the version stored in the database, then a + * {@link OConcurrentModificationException} exception is thrown. Before to save the document it must be valid following the + * constraints declared in the schema if any (can work also in schema-less mode). To validate the document the + * {@link ODocument#validate()} is called. + * + * @param iRecord Record to save + * @param iClusterName Cluster name where to save the record + * @param iMode Mode of save: synchronous (default) or asynchronous + * @param iForceCreate Flag that indicates that record should be created. If record with current rid already exists, + * exception is thrown + * @param iRecordCreatedCallback callback that is called after creation of new record + * @param iRecordUpdatedCallback callback that is called after record update + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * + * @throws OConcurrentModificationException if the version of the document is different by the version contained in the database. + * @throws OValidationException if the document breaks some validation constraints defined in the schema + * @see #setMVCC(boolean), {@link #isMVCC()}, ODocument#validate() + */ + @Override + public RET save(final ORecord iRecord, String iClusterName, final OPERATION_MODE iMode, + boolean iForceCreate, final ORecordCallback iRecordCreatedCallback, + ORecordCallback iRecordUpdatedCallback) { + checkOpeness(); + + if (!(iRecord instanceof ODocument)) { + assignAndCheckCluster(iRecord, iClusterName); + return (RET) currentTx.saveRecord(iRecord, iClusterName, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback); + } + + ODocument doc = (ODocument) iRecord; + ODocumentInternal.checkClass(doc, this); + // IN TX THE VALIDATION MAY BE RUN TWICE BUT IS CORRECT BECAUSE OF DIFFERENT RECORD STATUS + try { + doc.validate(); + } catch (OValidationException ex) { + doc.undo(); + throw ex; + } + ODocumentInternal.convertAllMultiValuesToTrackedVersions(doc); + + if (iForceCreate || !doc.getIdentity().isValid()) { + if (doc.getClassName() != null) + checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_CREATE, doc.getClassName()); + + assignAndCheckCluster(doc, iClusterName); + + } else { + // UPDATE: CHECK ACCESS ON SCHEMA CLASS NAME (IF ANY) + if (doc.getClassName() != null) + checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_UPDATE, doc.getClassName()); + } + + doc = (ODocument) currentTx + .saveRecord(iRecord, iClusterName, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback); + + return (RET) doc; + } + + /** + * Deletes a document. Behavior depends by the current running transaction if any. If no transaction is running then the record is + * deleted immediately. If an Optimistic transaction is running then the record will be deleted at commit time. The current + * transaction will continue to see the record as deleted, while others not. If a Pessimistic transaction is running, then an + * exclusive lock is acquired against the record. Current transaction will continue to see the record as deleted, while others + * cannot access to it since it's locked. + *

      + * If MVCC is enabled and the version of the document is different by the version stored in the database, then a + * {@link OConcurrentModificationException} exception is thrown. + * + * @param record record to delete + * + * @return The Database instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * + * @see #setMVCC(boolean), {@link #isMVCC()} + */ + public ODatabaseDocumentTx delete(final ORecord record) { + checkOpeness(); + if (record == null) + throw new ODatabaseException("Cannot delete null document"); + + // CHECK ACCESS ON SCHEMA CLASS NAME (IF ANY) + if (record instanceof ODocument && ((ODocument) record).getClassName() != null) + checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_DELETE, ((ODocument) record).getClassName()); + + try { + currentTx.deleteRecord(record, OPERATION_MODE.SYNCHRONOUS); + } catch (OException e) { + throw e; + } catch (Exception e) { + if (record instanceof ODocument) + throw OException.wrapException(new ODatabaseException( + "Error on deleting record " + record.getIdentity() + " of class '" + ((ODocument) record).getClassName() + "'"), e); + else + throw OException.wrapException(new ODatabaseException("Error on deleting record " + record.getIdentity()), e); + } + return this; + } + + /** + * Returns the number of the records of the class iClassName. + */ + public long countClass(final String iClassName) { + return countClass(iClassName, true); + } + + /** + * Returns the number of the records of the class iClassName considering also sub classes if polymorphic is true. + */ + public long countClass(final String iClassName, final boolean iPolymorphic) { + final OClass cls = getMetadata().getImmutableSchemaSnapshot().getClass(iClassName); + + if (cls == null) + throw new IllegalArgumentException("Class '" + iClassName + "' not found in database"); + + long totalOnDb = cls.count(iPolymorphic); + + long deletedInTx = 0; + long addedInTx = 0; + if (getTransaction().isActive()) + for (ORecordOperation op : getTransaction().getAllRecordEntries()) { + if (op.type == ORecordOperation.DELETED) { + final ORecord rec = op.getRecord(); + if (rec != null && rec instanceof ODocument) { + OClass schemaClass = ((ODocument) rec).getSchemaClass(); + if (iPolymorphic) { + if (schemaClass.isSubClassOf(iClassName)) + deletedInTx++; + } else { + if (iClassName.equals(schemaClass.getName()) || iClassName.equals(schemaClass.getShortName())) + deletedInTx++; + } + } + } + if (op.type == ORecordOperation.CREATED) { + final ORecord rec = op.getRecord(); + if (rec != null && rec instanceof ODocument) { + OClass schemaClass = ((ODocument) rec).getSchemaClass(); + if (iPolymorphic) { + if (schemaClass.isSubClassOf(iClassName)) + addedInTx++; + } else { + if (iClassName.equals(schemaClass.getName()) || iClassName.equals(schemaClass.getShortName())) + addedInTx++; + } + } + } + } + + return (totalOnDb + addedInTx) - deletedInTx; + } + + /** + * {@inheritDoc} + */ + @Override + public ODatabase commit() { + return commit(false); + } + + @Override + public ODatabaseDocument commit(boolean force) throws OTransactionException { + checkOpeness(); + checkIfActive(); + + if (!currentTx.isActive()) + return this; + + if (!force && currentTx.amountOfNestedTxs() > 1) { + currentTx.commit(); + return this; + } + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onBeforeTxCommit(this); + } catch (Exception e) { + rollback(force); + + OLogManager.instance().error(this, "Cannot commit the transaction: caught exception on execution of %s.onBeforeTxCommit()", + listener.getClass().getName(), e); + throw OException.wrapException(new OTransactionException( + "Cannot commit the transaction: caught exception on execution of " + listener.getClass().getName() + + "#onBeforeTxCommit()"), e); + } + + try { + currentTx.commit(force); + } catch (RuntimeException e) { + OLogManager.instance().debug(this, "Error on transaction commit", e); + + // WAKE UP ROLLBACK LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onBeforeTxRollback(this); + } catch (Throwable t) { + OLogManager.instance().error(this, "Error before transaction rollback", t); + } + + // ROLLBACK TX AT DB LEVEL + currentTx.rollback(false, 0); + getLocalCache().clear(); + + activateOnCurrentThread(); + + // WAKE UP ROLLBACK LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onAfterTxRollback(this); + } catch (Throwable t) { + OLogManager.instance().error(this, "Error after transaction rollback", t); + } + throw e; + } + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onAfterTxCommit(this); + } catch (Exception e) { + final String message = + "Error after the transaction has been committed. The transaction remains valid. The exception caught was on execution of " + + listener.getClass() + ".onAfterTxCommit()"; + + OLogManager.instance().error(this, message, e); + + throw OException.wrapException(new OTransactionBlockedException(message), e); + + } + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ODatabase rollback() { + return rollback(false); + } + + @Override + public ODatabaseDocument rollback(boolean force) throws OTransactionException { + checkOpeness(); + if (currentTx.isActive()) { + + if (!force && currentTx.amountOfNestedTxs() > 1) { + currentTx.rollback(); + return this; + } + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onBeforeTxRollback(this); + } catch (Throwable t) { + OLogManager.instance().error(this, "Error before transactional rollback", t); + } + + currentTx.rollback(force, -1); + + // WAKE UP LISTENERS + for (ODatabaseListener listener : browseListeners()) + try { + listener.onAfterTxRollback(this); + } catch (Throwable t) { + OLogManager.instance().error(this, "Error after transaction rollback", t); + } + } + + getLocalCache().clear(); + + return this; + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + @Override + public DB getUnderlying() { + throw new UnsupportedOperationException(); + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + @Override + public OStorage getStorage() { + return storage; + } + + /** + * This method is internal, it can be subject to signature change or be removed, do not use. + * + * @Internal + */ + @Override + public void replaceStorage(OStorage iNewStorage) { + storage = iNewStorage; + } + + @Override + public V callInLock(final Callable iCallable, final boolean iExclusiveLock) { + return storage.callInLock(iCallable, iExclusiveLock); + } + + @Override + public List backup(final OutputStream out, final Map options, final Callable callable, + final OCommandOutputListener iListener, final int compressionLevel, final int bufferSize) throws IOException { + return storage.backup(out, options, callable, iListener, compressionLevel, bufferSize); + } + + @Override + public void restore(final InputStream in, final Map options, final Callable callable, + final OCommandOutputListener iListener) throws IOException { + if (storage == null) + storage = Orient.instance().loadStorage(url); + + storage.restore(in, options, callable, iListener); + + if (!isClosed()) + getMetadata().reload(); + } + + /** + * {@inheritDoc} + */ + public OSBTreeCollectionManager getSbTreeCollectionManager() { + return getStorage().getSBtreeCollectionManager(); + } + + @Override + public OCurrentStorageComponentsFactory getStorageVersions() { + return componentsFactory; + } + + public ORecordSerializer getSerializer() { + return serializer; + } + + /** + * Sets serializer for the database which will be used for document serialization. + * + * @param serializer the serializer to set. + */ + public void setSerializer(ORecordSerializer serializer) { + this.serializer = serializer; + } + + @Override + public void resetInitialization() { + for (ORecordHook h : hooks.keySet()) + h.onUnregister(); + + hooks.clear(); + compileHooks(); + + close(); + + initialized = false; + } + + @Override + public String incrementalBackup(final String path) { + checkOpeness(); + checkIfActive(); + + return storage.incrementalBackup(path); + } + + @Override + @Deprecated + public DB checkSecurity(final String iResource, final int iOperation) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + checkSecurity(resourceGeneric, null, iOperation); + + return checkSecurity(resourceGeneric, resourceSpecific, iOperation); + } + + @Override + @Deprecated + public DB checkSecurity(final String iResourceGeneric, final int iOperation, + final Object iResourceSpecific) { + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResourceGeneric); + if (iResourceSpecific == null || iResourceSpecific.equals("*")) + return checkSecurity(resourceGeneric, iOperation, (Object) null); + + return checkSecurity(resourceGeneric, iOperation, iResourceSpecific); + } + + @Override + @Deprecated + public DB checkSecurity(final String iResourceGeneric, final int iOperation, + final Object... iResourcesSpecific) { + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResourceGeneric); + return checkSecurity(resourceGeneric, iOperation, iResourcesSpecific); + } + + /** + * @return true if database is obtained from the pool and false otherwise. + */ + @Override + public boolean isPooled() { + return false; + } + + /** + * Use #activateOnCurrentThread instead. + */ + @Deprecated + public void setCurrentDatabaseInThreadLocal() { + activateOnCurrentThread(); + } + + /** + * Activates current database instance on current thread. + */ + @Override + public ODatabaseDocumentTx activateOnCurrentThread() { + final ODatabaseRecordThreadLocal tl = ODatabaseRecordThreadLocal.INSTANCE; + if (tl != null) + tl.set(this); + return this; + } + + @Override + public boolean isActiveOnCurrentThread() { + final ODatabaseRecordThreadLocal tl = ODatabaseRecordThreadLocal.INSTANCE; + final ODatabaseDocumentInternal db = tl != null ? tl.getIfDefined() : null; + return db == this; + } + + protected void checkOpeness() { + if (isClosed()) + throw new ODatabaseException("Database '" + getURL() + "' is closed"); + } + + private void popInHook(OIdentifiable id) { + inHook.remove(id); + } + + private boolean pushInHook(OIdentifiable id) { + return inHook.add(id); + } + + private void initAtFirstOpen(String iUserName, String iUserPassword) { + if (initialized) + return; + + ORecordSerializerFactory serializerFactory = ORecordSerializerFactory.instance(); + String serializeName = getStorage().getConfiguration().getRecordSerializer(); + if (serializeName == null) { + throw new ODatabaseException( + "Database created with orientdb version not supported anymore, use export+import to migrate the database"); + } + serializer = serializerFactory.getFormat(serializeName); + if (serializer == null) + throw new ODatabaseException("RecordSerializer with name '" + serializeName + "' not found "); + if (getStorage().getConfiguration().getRecordSerializerVersion() > serializer.getMinSupportedVersion()) + throw new ODatabaseException("Persistent record serializer version is not support by the current implementation"); + + componentsFactory = getStorage().getComponentsFactory(); + + localCache.startup(); + + user = null; + + metadata = new OMetadataDefault(this); + metadata.load(); + + recordFormat = DEF_RECORD_FORMAT; + + if (!(getStorage() instanceof OStorageProxy)) { + if (metadata.getIndexManager().autoRecreateIndexesAfterCrash()) { + metadata.getIndexManager().recreateIndexes(); + + activateOnCurrentThread(); + user = null; + } + + installHooksEmbedded(); + registerHook(new OCommandCacheHook(this), ORecordHook.HOOK_POSITION.REGULAR); + registerHook(new OSecurityTrackerHook(metadata.getSecurity(), this), ORecordHook.HOOK_POSITION.LAST); + + user = null; + } else if (iUserName != null && iUserPassword != null) { + user = new OImmutableUser(-1, new OUser(iUserName, OUser.encryptPassword(iUserPassword)) + .addRole(new ORole("passthrough", null, ORole.ALLOW_MODES.ALLOW_ALL_BUT))); + installHooksRemote(); + } + + initialized = true; + } + + private void installHooksEmbedded() { + hooks.clear(); + registerHook(new OClassTrigger(this), ORecordHook.HOOK_POSITION.FIRST); + registerHook(new ORestrictedAccessHook(this), ORecordHook.HOOK_POSITION.FIRST); + registerHook(new OUserTrigger(this), ORecordHook.HOOK_POSITION.EARLY); + registerHook(new OFunctionTrigger(this), ORecordHook.HOOK_POSITION.REGULAR); + registerHook(new OSequenceTrigger(this), ORecordHook.HOOK_POSITION.REGULAR); + registerHook(new OClassIndexManager(this), ORecordHook.HOOK_POSITION.LAST); + registerHook(new OSchedulerTrigger(this), ORecordHook.HOOK_POSITION.LAST); + registerHook(new OLiveQueryHook(this), ORecordHook.HOOK_POSITION.LAST); + } + + private void installHooksRemote() { + hooks.clear(); + registerHook(new ClassIndexManagerRemote(this), ORecordHook.HOOK_POSITION.LAST); + } + + private void closeOnDelete() { + if (status != STATUS.OPEN) + return; + + if (currentIntent != null) { + currentIntent.end(this); + currentIntent = null; + } + + resetListeners(); + + if (storage != null) + storage.close(true, true); + + storage = null; + status = STATUS.CLOSED; + } + + private void clearCustomInternal() { + storage.getConfiguration().clearProperties(); + } + + private void removeCustomInternal(final String iName) { + setCustomInternal(iName, null); + } + + private void setCustomInternal(final String iName, final String iValue) { + if (iValue == null || "null".equalsIgnoreCase(iValue)) + // REMOVE + storage.getConfiguration().removeProperty(iName); + else + // SET + storage.getConfiguration().setProperty(iName, iValue); + + storage.getConfiguration().update(); + } + + private void callbackHookFailure(ORecord record, boolean wasNew, byte[] stream) { + if (stream != null && stream.length > 0) + callbackHooks(wasNew ? ORecordHook.TYPE.CREATE_FAILED : ORecordHook.TYPE.UPDATE_FAILED, record); + } + + private void callbackHookSuccess(final ORecord record, final boolean wasNew, final byte[] stream, + final OStorageOperationResult operationResult) { + if (stream != null && stream.length > 0) { + final ORecordHook.TYPE hookType; + if (!operationResult.isMoved()) { + hookType = wasNew ? ORecordHook.TYPE.AFTER_CREATE : ORecordHook.TYPE.AFTER_UPDATE; + } else { + hookType = wasNew ? ORecordHook.TYPE.CREATE_REPLICATED : ORecordHook.TYPE.UPDATE_REPLICATED; + } + callbackHooks(hookType, record); + + } + } + + private void callbackHookFinalize(final ORecord record, final boolean wasNew, final byte[] stream) { + if (stream != null && stream.length > 0) { + final ORecordHook.TYPE hookType; + hookType = wasNew ? ORecordHook.TYPE.FINALIZE_CREATION : ORecordHook.TYPE.FINALIZE_UPDATE; + callbackHooks(hookType, record); + + clearDocumentTracking(record); + } + } + + private void clearDocumentTracking(final ORecord record) { + if (record instanceof ODocument && ((ODocument) record).isTrackingChanges()) { + ODocumentInternal.clearTrackData((ODocument) record); + } + } + + private void checkRecordClass(final OClass recordClass, final String iClusterName, final ORecordId rid) { + if (getStorageVersions().classesAreDetectedByClusterId()) { + final OClass clusterIdClass = metadata.getImmutableSchemaSnapshot().getClassByClusterId(rid.getClusterId()); + if (recordClass == null && clusterIdClass != null || clusterIdClass == null && recordClass != null || (recordClass != null + && !recordClass.equals(clusterIdClass))) + throw new IllegalArgumentException( + "Record saved into cluster '" + iClusterName + "' should be saved with class '" + clusterIdClass + + "' but has been created with class '" + recordClass + "'"); + } + } + + private byte[] updateStream(final ORecord record) { + ORecordSerializationContext.pullContext(); + + ODirtyManager manager = ORecordInternal.getDirtyManager(record); + Set newRecords = manager.getNewRecords(); + Set updatedRecords = manager.getUpdateRecords(); + manager.clearForSave(); + if (newRecords != null) { + for (ORecord newRecord : newRecords) { + if (newRecord != record) + getTransaction().saveRecord(newRecord, null, OPERATION_MODE.SYNCHRONOUS, false, null, null); + } + } + if (updatedRecords != null) { + for (ORecord updatedRecord : updatedRecords) { + if (updatedRecord != record) + getTransaction().saveRecord(updatedRecord, null, OPERATION_MODE.SYNCHRONOUS, false, null, null); + } + } + + ORecordSerializationContext.pushContext(); + ORecordInternal.unsetDirty(record); + record.setDirty(); + return record.toStream(); + } + + private void init() { + currentTx = new OTransactionNoTx(this); + } + + private OFreezableStorageComponent getFreezableStorage() { + OStorage s = getStorage(); + if (s instanceof OFreezableStorageComponent) + return (OFreezableStorageComponent) s; + else { + OLogManager.instance().error(this, "Storage of type " + s.getType() + " does not support freeze operation"); + return null; + } + } + + /** + * @Internal + */ + public interface RecordReader { + ORawBuffer readRecord(OStorage storage, ORecordId rid, String fetchPlan, boolean ignoreCache, final int recordVersion) + throws ORecordNotFoundException; + } + + /** + * @Internal + */ + public static final class SimpleRecordReader implements RecordReader { + private final boolean prefetchRecords; + + public SimpleRecordReader(boolean prefetchRecords) { + this.prefetchRecords = prefetchRecords; + } + + @Override + public ORawBuffer readRecord(OStorage storage, ORecordId rid, String fetchPlan, boolean ignoreCache, final int recordVersion) + throws ORecordNotFoundException { + return storage.readRecord(rid, fetchPlan, ignoreCache, prefetchRecords, null).getResult(); + } + } + + /** + * @Internal + */ + public static final class LatestVersionRecordReader implements RecordReader { + @Override + public ORawBuffer readRecord(OStorage storage, ORecordId rid, String fetchPlan, boolean ignoreCache, final int recordVersion) + throws ORecordNotFoundException { + return storage.readRecordIfVersionIsNotLatest(rid, fetchPlan, ignoreCache, recordVersion).getResult(); + } + + } + + public void checkIfActive() { + final ODatabaseRecordThreadLocal tl = ODatabaseRecordThreadLocal.INSTANCE; + final ODatabaseDocumentInternal currentDatabase = tl != null ? tl.getIfDefined() : null; + if (currentDatabase != this) + throw new IllegalStateException( + "The current database instance (" + toString() + ") is not active on the current thread (" + Thread.currentThread() + + "). Current active database is: " + currentDatabase); + } + + @Override + public int addBlobCluster(final String iClusterName, final Object... iParameters) { + int id; + if (getStorage() instanceof OStorageProxy) { + id = command(new OCommandSQL("create blob cluster :1")).execute(iClusterName); + getMetadata().getSchema().reload(); + } else { + if (!existsCluster(iClusterName)) { + id = addCluster(iClusterName, iParameters); + } else + id = getClusterIdByName(iClusterName); + getMetadata().getSchema().addBlobCluster(id); + } + return id; + } + + public Set getBlobClusterIds() { + return getMetadata().getSchema().getBlobClusters(); + } + + private void compileHooks() { + final List[] intermediateHooksByScope = new List[ORecordHook.SCOPE.values().length]; + for (ORecordHook.SCOPE scope : ORecordHook.SCOPE.values()) + intermediateHooksByScope[scope.ordinal()] = new ArrayList(); + + for (ORecordHook hook : hooks.keySet()) + for (ORecordHook.SCOPE scope : hook instanceof ORecordHook.Scoped ? + ((ORecordHook.Scoped) hook).getScopes() : + ORecordHook.SCOPE.values()) + intermediateHooksByScope[scope.ordinal()].add(hook); + + for (ORecordHook.SCOPE scope : ORecordHook.SCOPE.values()) { + final int ordinal = scope.ordinal(); + final List scopeHooks = intermediateHooksByScope[ordinal]; + hooksByScope[ordinal] = scopeHooks.toArray(new ORecordHook[scopeHooks.size()]); + } + } + + public static Object executeWithRetries(final OCallable callback, final int maxRetry) { + return executeWithRetries(callback, maxRetry, 0, null); + } + + public static Object executeWithRetries(final OCallable callback, final int maxRetry, + final int waitBetweenRetry) { + return executeWithRetries(callback, maxRetry, waitBetweenRetry, null); + } + + public static Object executeWithRetries(final OCallable callback, final int maxRetry, final int waitBetweenRetry, + final ORecord[] recordToReloadOnRetry) { + ONeedRetryException lastException = null; + for (int retry = 0; retry < maxRetry; ++retry) { + try { + return callback.call(retry); + } catch (ONeedRetryException e) { + // SAVE LAST EXCEPTION AND RETRY + lastException = e; + + if (recordToReloadOnRetry != null) { + // RELOAD THE RECORDS + for (ORecord r : recordToReloadOnRetry) + r.reload(); + } + + if (waitBetweenRetry > 0) + try { + Thread.sleep(waitBetweenRetry); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + break; + } + } + } + throw lastException; + } + + public OIntent getActiveIntent() { + return currentIntent; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxInternal.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxInternal.java new file mode 100644 index 00000000000..fcaebdc08a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxInternal.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.orient.core.db.ODatabaseSessionMetadata; + +/** + * Created by tglman on 31/03/16. + */ +public class ODatabaseDocumentTxInternal { + + private ODatabaseDocumentTxInternal() { + } + + public static ODatabaseSessionMetadata getSessionMetadata(final ODatabaseDocumentTx db) { + return db.sessionMetadata; + } + + public static void setSessionMetadata(final ODatabaseDocumentTx db, final ODatabaseSessionMetadata sessionMetadata) { + db.sessionMetadata = sessionMetadata; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxPooled.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxPooled.java new file mode 100755 index 00000000000..372d64f1c14 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODatabaseDocumentTxPooled.java @@ -0,0 +1,156 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabasePoolBase; +import com.orientechnologies.orient.core.db.ODatabasePooled; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.metadata.security.OToken; + +/** + * Pooled wrapper to the ODatabaseDocumentTx class. Allows to being reused across calls. The close() method does not close the + * database for real but release it to the owner pool. The database born as opened and will leave open until the pool is closed. + * + * @author Luca Garulli + * @see ODatabasePoolBase + */ +@SuppressWarnings("unchecked") +public class ODatabaseDocumentTxPooled extends ODatabaseDocumentTx implements ODatabasePooled { + + private ODatabaseDocumentPool ownerPool; + private String userName; + + public ODatabaseDocumentTxPooled(final ODatabaseDocumentPool iOwnerPool, final String iURL, final String iUserName, + final String iUserPassword) { + super(iURL); + ownerPool = iOwnerPool; + userName = iUserName; + + super.open(iUserName, iUserPassword); + } + + public void reuse(final Object iOwner, final Object[] iAdditionalArgs) { + ownerPool = (ODatabaseDocumentPool) iOwner; + getLocalCache().invalidate(); + // getMetadata().reload(); + ODatabaseRecordThreadLocal.INSTANCE.set(this); + + try { + callOnOpenListeners(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on reusing database '%s' in pool", e, getName()); + } + } + + @Override + public ODatabaseDocumentTxPooled open(final String iUserName, final String iUserPassword) { + throw new UnsupportedOperationException( + "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection"); + } + + @Override + public ODatabaseDocumentTxPooled open(final OToken iToken) { + throw new UnsupportedOperationException( + "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection"); + } + + @Override + public ODatabaseDocumentTxPooled create() { + throw new UnsupportedOperationException( + "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection"); + } + + @Override + public DB create(String incrementalBackupPath) { + throw new UnsupportedOperationException( + "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection"); + } + + public boolean isUnderlyingOpen() { + return !super.isClosed(); + } + + @Override + public boolean isClosed() { + return ownerPool == null || super.isClosed(); + } + + /** + * @return true if database is obtained from the pool and false otherwise. + */ + @Override + public boolean isPooled() { + return true; + } + + /** + * Avoid to close it but rather release itself to the owner pool. + */ + @Override + public void close() { + if (isClosed()) + return; + + checkOpeness(); + + if (ownerPool != null && ownerPool.getConnectionsInCurrentThread(getURL(), userName) > 1) { + ownerPool.release(this); + return; + } + + try { + commit(true); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on releasing database '%s' in pool", e, getName()); + } + + try { + callOnCloseListeners(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on releasing database '%s' in pool", e, getName()); + } + + getLocalCache().clear(); + + if (ownerPool != null) { + final ODatabaseDocumentPool localCopy = ownerPool; + ownerPool = null; + localCopy.release(this); + } + + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } + + public void forceClose() { + super.close(); + } + + @Override + protected void checkOpeness() { + if (ownerPool == null) + throw new ODatabaseException( + "Database instance has been released to the pool. Get another database instance from the pool with the right username and password"); + + super.checkOpeness(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldVisitor.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldVisitor.java new file mode 100755 index 00000000000..3d026b4aa84 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldVisitor.java @@ -0,0 +1,78 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Is used in together with {@link com.orientechnologies.orient.core.db.document.ODocumentFieldWalker} to visit all fields of + * current document. + */ +public interface ODocumentFieldVisitor { + /** + * Visits currently processed field. + * + * @param type + * Filed type. May be null if absent in DB schema. + * @param linkedType + * Linked type in case collection is processed. May be null if absent in DB schema. + * @param value + * Field value. + * @return New value of this field. If the same value is returned document content will not be changed. + */ + Object visitField(OType type, OType linkedType, Object value); + + /** + * Indicates whether we continue to visit document fields after current one or should stop fields processing. + * + * @param type + * Filed type. May be null if absent in DB schema. + * @param linkedType + * Linked type in case collection is processed. May be null if absent in DB schema. + * @param value + * Field value. + * @param newValue + * New value returned by {@link #visitField(OType, OType, Object)} method. + * + * @return If false document processing will be stopped. + */ + boolean goFurther(OType type, OType linkedType, Object value, Object newValue); + + /** + * If currently processed value is collection or map of embedded documents or embedded document itself then current method is + * called if it returns false then this collection will not be visited. + * + * @param type + * Filed type. May be null if absent in DB schema. + * @param linkedType + * Linked type in case collection is processed. May be null if absent in DB schema. + * @param value + * Field value. + * @return If false currently processed collection of embedded documents will not be visited. + */ + boolean goDeeper(OType type, OType linkedType, Object value); + + /** + * @return If false value returned by method {@link #visitField(OType, OType, Object)} will not be taken in account and field + * value will not be updated. + */ + boolean updateMode(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldWalker.java b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldWalker.java new file mode 100755 index 00000000000..871dc7b9563 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/document/ODocumentFieldWalker.java @@ -0,0 +1,148 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.document; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +import java.util.*; + +/** + * This class allows to walk through all fields of single document using instance of {@link ODocumentFieldVisitor} class. + * + * Only current document and embedded documents will be walked. Which means that all embedded collections will be visited too and + * all embedded documents which are contained in this collections also will be visited. + * + * Fields values can be updated/converted too. If method {@link ODocumentFieldVisitor#visitField(OType, OType, Object)} will return + * new value original value will be updated but returned result will not be visited by {@link ODocumentFieldVisitor} instance. + * + * If currently processed value is collection or map of embedded documents or embedded document itself then method + * {@link ODocumentFieldVisitor#goDeeper(OType, OType, Object)} is called, if it returns false then this collection will not be + * visited by {@link ODocumentFieldVisitor} instance. + * + * Fields will be visited till method {@link ODocumentFieldVisitor#goFurther(OType, OType, Object, Object)} returns true. + */ +public class ODocumentFieldWalker { + public void walkDocument(ODocument document, ODocumentFieldVisitor fieldWalker) { + final Set walked = Collections.newSetFromMap(new IdentityHashMap()); + walkDocument(document, fieldWalker, walked); + walked.clear(); + } + + private void walkDocument(ODocument document, ODocumentFieldVisitor fieldWalker, Set walked) { + if (walked.contains(document)) + return; + + walked.add(document); + boolean oldLazyLoad = document.isLazyLoad(); + document.setLazyLoad(false); + + final boolean updateMode = fieldWalker.updateMode(); + + final OClass clazz = ODocumentInternal.getImmutableSchemaClass(document); + for (String fieldName : document.fieldNames()) { + + final OType concreteType = document.fieldType(fieldName); + OType fieldType = concreteType; + + OType linkedType = null; + if (fieldType == null && clazz != null) { + OProperty property = clazz.getProperty(fieldName); + if (property != null) { + fieldType = property.getType(); + linkedType = property.getLinkedType(); + } + } + + Object fieldValue = document.field(fieldName, fieldType); + Object newValue = fieldWalker.visitField(fieldType, linkedType, fieldValue); + + boolean updated; + if (updateMode) + updated = updateFieldValueIfChanged(document, fieldName, fieldValue, newValue, concreteType); + else + updated = false; + + // exclude cases when: + // 1. value was updated. + // 2. we use link types. + // 3. document is not not embedded. + if (!updated + && fieldValue != null + && !(OType.LINK.equals(fieldType) || OType.LINKBAG.equals(fieldType) || OType.LINKLIST.equals(fieldType) + || OType.LINKSET.equals(fieldType) || (fieldValue instanceof ORecordLazyMultiValue))) { + if (fieldWalker.goDeeper(fieldType, linkedType, fieldValue)) { + if (fieldValue instanceof Map) + walkMap((Map) fieldValue, fieldType, fieldWalker, walked); + else if (fieldValue instanceof ODocument) { + final ODocument doc = (ODocument) fieldValue; + if (OType.EMBEDDED.equals(fieldType) || doc.isEmbedded()) + walkDocument((ODocument) fieldValue, fieldWalker); + } else if (OMultiValue.isIterable(fieldValue)) + walkIterable(OMultiValue.getMultiValueIterable(fieldValue), fieldType, fieldWalker, walked); + } + } + + if (!fieldWalker.goFurther(fieldType, linkedType, fieldValue, newValue)) { + document.setLazyLoad(oldLazyLoad); + return; + } + } + + document.setLazyLoad(oldLazyLoad); + } + + private void walkMap(Map map, OType fieldType, ODocumentFieldVisitor fieldWalker, Set walked) { + for (Object value : map.values()) { + if (value instanceof ODocument) { + final ODocument doc = (ODocument) value; + // only embedded documents are walked + if (OType.EMBEDDEDMAP.equals(fieldType) || doc.isEmbedded()) + walkDocument((ODocument) value, fieldWalker, walked); + } + } + } + + private void walkIterable(Iterable iterable, OType fieldType, ODocumentFieldVisitor fieldWalker, Set walked) { + for (Object value : iterable) { + if (value instanceof ODocument) { + final ODocument doc = (ODocument) value; + // only embedded documents are walked + if (OType.EMBEDDEDLIST.equals(fieldType) || OType.EMBEDDEDSET.equals(fieldType) || doc.isEmbedded()) + walkDocument((ODocument) value, fieldWalker, walked); + } + } + } + + private boolean updateFieldValueIfChanged(ODocument document, String fieldName, Object fieldValue, Object newValue, + OType concreteType) { + if (fieldValue != newValue) { + document.field(fieldName, newValue, concreteType); + return true; + } + + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/object/ODatabaseObject.java b/core/src/main/java/com/orientechnologies/orient/core/db/object/ODatabaseObject.java new file mode 100644 index 00000000000..7aadc6345b7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/object/ODatabaseObject.java @@ -0,0 +1,99 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.object; + +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseSchemaAware; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.entity.OEntityManager; +import com.orientechnologies.orient.core.iterator.object.OObjectIteratorClassInterface; +import com.orientechnologies.orient.core.iterator.object.OObjectIteratorClusterInterface; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Generic interface for object based Database implementations. Binds to/from Document and POJOs. + * + * @author Luca Garulli + */ +public interface ODatabaseObject extends ODatabaseSchemaAware, OUserObject2RecordHandler { + + /** + * Sets as dirty a POJO. This is useful when you change the object and need to tell to the engine to treat as dirty. + * + * @param iPojo + * User object + */ + void setDirty(final Object iPojo); + + /** + * Sets as not dirty a POJO. This is useful when you change some other object and need to tell to the engine to treat this one as + * not dirty. + * + * @param iPojo + * User object + */ + void unsetDirty(final Object iPojo); + + /** + * Browses all the records of the specified cluster. + * + * @param iClusterName + * Cluster name to iterate + * @return Iterator of Object instances + */ + OObjectIteratorClusterInterface browseCluster(String iClusterName); + + /** + * Browses all the records of the specified class. + * + * @param iClusterClass + * Class name to iterate + * @return Iterator of Object instances + */ + OObjectIteratorClassInterface browseClass(Class iClusterClass); + + /** + * Creates a new entity of the specified class. + * + * @param iType + * Class name where to originate the instance + * @return New instance + */ + T newInstance(Class iType); + + /** + * Returns the entity manager that handle the binding from ODocuments and POJOs. + * + * @return + */ + OEntityManager getEntityManager(); + + boolean isRetainObjects(); + + ODatabase setRetainObjects(boolean iRetainObjects); + + Object stream2pojo(ODocument iRecord, final Object iPojo, final String iFetchPlan); + + ODocument pojo2Stream(final Object iPojo, final ODocument iRecord); + + boolean isLazyLoading(); + + void setLazyLoading(final boolean lazyLoading); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectListInterface.java b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectListInterface.java new file mode 100644 index 00000000000..7444f846fd4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectListInterface.java @@ -0,0 +1,31 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.db.object; + +import java.util.List; + +/** + * @author luca.molino + * + */ +public interface OLazyObjectListInterface extends List { + + public void setConvertToRecord(boolean convertToRecord); + + public boolean isConverted(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectMapInterface.java b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectMapInterface.java new file mode 100644 index 00000000000..0714b4744d3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectMapInterface.java @@ -0,0 +1,31 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.db.object; + +import java.util.Map; + +/** + * @author luca.molino + * + */ +public interface OLazyObjectMapInterface extends Map { + + public void setConvertToRecord(boolean convertToRecord); + + public boolean isConverted(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectSetInterface.java b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectSetInterface.java new file mode 100644 index 00000000000..015ec979331 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/object/OLazyObjectSetInterface.java @@ -0,0 +1,31 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.db.object; + +import java.util.Set; + +/** + * @author luca.molino + * + */ +public interface OLazyObjectSetInterface extends Set { + + public void setConvertToRecord(boolean convertToRecord); + + public boolean isConverted(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/object/OObjectLazyMultivalueElement.java b/core/src/main/java/com/orientechnologies/orient/core/db/object/OObjectLazyMultivalueElement.java new file mode 100644 index 00000000000..e6314edb766 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/object/OObjectLazyMultivalueElement.java @@ -0,0 +1,34 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.db.object; + +import java.util.Map; + +/** + * @author luca.molino + * + */ +public interface OObjectLazyMultivalueElement { + + public void detach(boolean nonProxiedInstance); + + public void detachAll(boolean nonProxiedInstance, Map alreadyDetached, Map lazyObjects); + + public T getNonOrientInstance(); + + public Object getUnderlying(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OAutoConvertToRecord.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OAutoConvertToRecord.java new file mode 100644 index 00000000000..bb948dd4f3e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OAutoConvertToRecord.java @@ -0,0 +1,26 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +public interface OAutoConvertToRecord { + public boolean isAutoConvertToRecord(); + + public void setAutoConvertToRecord(boolean convertToRecord); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OClassTrigger.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OClassTrigger.java new file mode 100755 index 00000000000..251389d4faf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OClassTrigger.java @@ -0,0 +1,331 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.common.concur.resource.OPartitionedObjectPool; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.script.OCommandScriptException; +import com.orientechnologies.orient.core.command.script.OScriptManager; +import com.orientechnologies.orient.core.db.ODatabase.STATUS; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import javax.script.*; +import java.lang.reflect.Method; + +/** + * Author : henryzhao81@gmail.com Feb 19, 2013 + * + * Create a class OTriggered which contains 8 additional class attributes, which link to OFunction - beforeCreate - afterCreate - + * beforeRead - afterRead - beforeUpdate - afterUpdate - beforeDelete - afterDelete + */ +public class OClassTrigger extends ODocumentHookAbstract implements ORecordHook.Scoped { + public static final String CLASSNAME = "OTriggered"; + public static final String METHOD_SEPARATOR = "."; + + // Class Level Trigger (class custom attribute) + public static final String ONBEFORE_CREATED = "onBeforeCreate"; + // Record Level Trigger (property name) + public static final String PROP_BEFORE_CREATE = ONBEFORE_CREATED; + public static final String ONAFTER_CREATED = "onAfterCreate"; + public static final String PROP_AFTER_CREATE = ONAFTER_CREATED; + public static final String ONBEFORE_READ = "onBeforeRead"; + public static final String PROP_BEFORE_READ = ONBEFORE_READ; + public static final String ONAFTER_READ = "onAfterRead"; + public static final String PROP_AFTER_READ = ONAFTER_READ; + public static final String ONBEFORE_UPDATED = "onBeforeUpdate"; + public static final String PROP_BEFORE_UPDATE = ONBEFORE_UPDATED; + public static final String ONAFTER_UPDATED = "onAfterUpdate"; + public static final String PROP_AFTER_UPDATE = ONAFTER_UPDATED; + public static final String ONBEFORE_DELETE = "onBeforeDelete"; + public static final String PROP_BEFORE_DELETE = ONBEFORE_DELETE; + public static final String ONAFTER_DELETE = "onAfterDelete"; + public static final String PROP_AFTER_DELETE = ONAFTER_DELETE; + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.READ, SCOPE.UPDATE, SCOPE.DELETE }; + + public OClassTrigger(ODatabaseDocument database) { + super(database); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.SOURCE_NODE; + } + + @Override + public RESULT onRecordBeforeCreate(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONBEFORE_CREATED); + if (func != null) { + if (func instanceof OFunction) + return this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + return this.executeMethod(iDocument, (Object[]) func); + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterCreate(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONAFTER_CREATED); + if (func != null) { + if (func instanceof OFunction) + this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + this.executeMethod(iDocument, (Object[]) func); + } + } + + @Override + public RESULT onRecordBeforeRead(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONBEFORE_READ); + if (func != null) { + if (func instanceof OFunction) + return this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + return this.executeMethod(iDocument, (Object[]) func); + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterRead(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONAFTER_READ); + if (func != null) { + if (func instanceof OFunction) + this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + this.executeMethod(iDocument, (Object[]) func); + } + } + + @Override + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONBEFORE_UPDATED); + if (func != null) { + if (func instanceof OFunction) + return this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + return this.executeMethod(iDocument, (Object[]) func); + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterUpdate(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONAFTER_UPDATED); + if (func != null) { + if (func instanceof OFunction) + this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + this.executeMethod(iDocument, (Object[]) func); + } + } + + @Override + public RESULT onRecordBeforeDelete(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONBEFORE_DELETE); + if (func != null) { + if (func instanceof OFunction) + return this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + return this.executeMethod(iDocument, (Object[]) func); + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterDelete(final ODocument iDocument) { + Object func = this.checkClzAttribute(iDocument, ONAFTER_DELETE); + if (func != null) { + if (func instanceof OFunction) + this.executeFunction(iDocument, (OFunction) func); + else if (func instanceof Object[]) + this.executeMethod(iDocument, (Object[]) func); + } + } + + public RESULT onTrigger(final TYPE iType, final ORecord iRecord) { + if (database.getStatus() != STATUS.OPEN) + return RESULT.RECORD_NOT_CHANGED; + + if (!(iRecord instanceof ODocument)) + return RESULT.RECORD_NOT_CHANGED; + + final ODocument document = (ODocument) iRecord; + OImmutableClass immutableSchemaClass = ODocumentInternal.getImmutableSchemaClass(document); + if (immutableSchemaClass != null && immutableSchemaClass.isTriggered()) + return super.onTrigger(iType, iRecord); + + return RESULT.RECORD_NOT_CHANGED; + } + + private Object checkClzAttribute(final ODocument iDocument, String attr) { + final OImmutableClass clz = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (clz != null && clz.isTriggered()) { + OFunction func = null; + String fieldName = clz.getCustom(attr); + OClass superClz = clz.getSuperClass(); + while (fieldName == null || fieldName.length() == 0) { + if (superClz == null || superClz.getName().equals(CLASSNAME)) + break; + fieldName = superClz.getCustom(attr); + superClz = superClz.getSuperClass(); + } + if (fieldName != null && fieldName.length() > 0) { + // check if it is reflection or not + final Object[] clzMethod = this.checkMethod(fieldName); + if (clzMethod != null) + return clzMethod; + func = database.getMetadata().getFunctionLibrary().getFunction(fieldName); + if (func == null) { // check if it is rid + if (OStringSerializerHelper.contains(fieldName, ORID.SEPARATOR)) { + try { + ODocument funcDoc = database.load(new ORecordId(fieldName)); + if (funcDoc != null) { + func = database.getMetadata().getFunctionLibrary().getFunction((String) funcDoc.field("name")); + } + } catch (Exception ex) { + OLogManager.instance().error(this, "illegal record id : ", ex.getMessage()); + } + } + } + } else { + final Object funcProp = iDocument.field(attr); + if (funcProp != null) { + final String funcName = funcProp instanceof ODocument ? + (String) ((ODocument) funcProp).field("name") : + funcProp.toString(); + func = database.getMetadata().getFunctionLibrary().getFunction(funcName); + } + } + return func; + } + return null; + } + + private Object[] checkMethod(String fieldName) { + String clzName = null; + String methodName = null; + if (fieldName.contains(METHOD_SEPARATOR)) { + clzName = fieldName.substring(0, fieldName.lastIndexOf(METHOD_SEPARATOR)); + methodName = fieldName.substring(fieldName.lastIndexOf(METHOD_SEPARATOR) + 1); + } + if (clzName == null || methodName == null) + return null; + try { + Class clz = ClassLoader.getSystemClassLoader().loadClass(clzName); + Method method = clz.getMethod(methodName, ODocument.class); + return new Object[] { clz, method }; + } catch (Exception ex) { + OLogManager.instance().error(this, "illegal class or method : " + clzName + "/" + methodName); + return null; + } + } + + private RESULT executeMethod(final ODocument iDocument, final Object[] clzMethod) { + if (clzMethod[0] instanceof Class && clzMethod[1] instanceof Method) { + Method method = (Method) clzMethod[1]; + Class clz = (Class) clzMethod[0]; + String result = null; + try { + result = (String) method.invoke(clz.newInstance(), iDocument); + } catch (Exception ex) { + throw OException.wrapException(new ODatabaseException("Failed to invoke method " + method.getName()), ex); + } + if (result == null) { + return RESULT.RECORD_NOT_CHANGED; + } + return RESULT.valueOf(result); + } + return RESULT.RECORD_NOT_CHANGED; + } + + private RESULT executeFunction(final ODocument iDocument, final OFunction func) { + if (func == null) + return RESULT.RECORD_NOT_CHANGED; + + final OScriptManager scriptManager = Orient.instance().getScriptManager(); + + final OPartitionedObjectPool.PoolEntry entry = scriptManager + .acquireDatabaseEngine(database.getName(), func.getLanguage()); + final ScriptEngine scriptEngine = entry.object; + try { + final Bindings binding = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); + + scriptManager.bind(binding, (ODatabaseDocumentTx) database, null, null); + binding.put("doc", iDocument); + + String result = null; + try { + if (func.getLanguage() == null) + throw new OConfigurationException("Database function '" + func.getName() + "' has no language"); + final String funcStr = scriptManager.getFunctionDefinition(func); + if (funcStr != null) { + try { + scriptEngine.eval(funcStr); + } catch (ScriptException e) { + scriptManager.throwErrorMessage(e, funcStr); + } + } + if (scriptEngine instanceof Invocable) { + final Invocable invocableEngine = (Invocable) scriptEngine; + Object[] EMPTY = OCommonConst.EMPTY_OBJECT_ARRAY; + result = (String) invocableEngine.invokeFunction(func.getName(), EMPTY); + } + } catch (ScriptException e) { + throw OException + .wrapException(new OCommandScriptException("Error on execution of the script", func.getName(), e.getColumnNumber()), e); + } catch (NoSuchMethodException e) { + throw OException.wrapException(new OCommandScriptException("Error on execution of the script", func.getName(), 0), e); + } catch (OCommandScriptException e) { + // PASS THROUGH + throw e; + + } finally { + scriptManager.unbind(binding, null, null); + } + if (result == null) { + return RESULT.RECORD_NOT_CHANGED; + } + return RESULT.valueOf(result); + + } finally { + scriptManager.releaseDatabaseEngine(func.getLanguage(), database.getName(), entry); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OCurrentStorageComponentsFactory.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OCurrentStorageComponentsFactory.java new file mode 100644 index 00000000000..408eb304f02 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OCurrentStorageComponentsFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; + +/** + * The factory that defines a set of components that current database should use to be compatible to current version of storage. So + * if you open a database create with old version of OrientDB it defines a components that should be used to provide backward + * compatibility with that version of database. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 2/14/14 + */ +public class OCurrentStorageComponentsFactory { + public final int binaryFormatVersion; + public final OBinarySerializerFactory binarySerializerFactory; + + public OCurrentStorageComponentsFactory(OStorageConfiguration configuration) { + this.binaryFormatVersion = configuration.binaryFormatVersion; + + binarySerializerFactory = OBinarySerializerFactory.create(binaryFormatVersion); + } + + /** + * @return Whether class of is detected by cluster id or it is taken from documents serialized content. + * @since 1.7 + */ + public boolean classesAreDetectedByClusterId() { + return binaryFormatVersion >= 10; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ODetachable.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ODetachable.java new file mode 100644 index 00000000000..5a0f7f0ee9e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ODetachable.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +/** + * Objects of this class can be detached. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ODetachable { + /** + * Detaches the object. + * + * @return true if the object has been fully detached, otherwise false + */ + public boolean detach(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentifiable.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentifiable.java new file mode 100755 index 00000000000..2162440f501 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentifiable.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Comparator; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.storage.OStorage; + +/** + * Base interface for identifiable objects. This abstraction is required to use ORID and ORecord in many points. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OIdentifiable extends Comparable, Comparator { + /** + * Returns the record identity. + * + * @return ORID instance + */ + ORID getIdentity(); + + /** + * Returns the record instance. + * + * @return ORecord instance + */ + T getRecord(); + + void lock(boolean iExclusive); + + boolean isLocked(); + + OStorage.LOCKING_STRATEGY lockingStrategy(); + + void unlock(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentityChangeListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentityChangeListener.java new file mode 100755 index 00000000000..cb72050f793 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OIdentityChangeListener.java @@ -0,0 +1,25 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record; + +public interface OIdentityChangeListener { + public void onBeforeIdentityChanged(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordIterator.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordIterator.java new file mode 100644 index 00000000000..5849a72028b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordIterator.java @@ -0,0 +1,121 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Iterator; + +import com.orientechnologies.common.collection.OLazyIterator; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; + +/** + * Lazy implementation of Iterator that load the records only when accessed. It keep also track of changes to the source record + * avoiding to call setDirty() by hand. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OLazyRecordIterator implements OLazyIterator, OResettable { + final private ORecord sourceRecord; + final private Iterable source; + private Iterator underlying; + final private boolean autoConvert2Record; + + public OLazyRecordIterator(final Iterator iIterator, final boolean iConvertToRecord) { + this.sourceRecord = null; + this.underlying = iIterator; + this.autoConvert2Record = iConvertToRecord; + this.source = null; + } + + public OLazyRecordIterator(final ORecord iSourceRecord, final Iterator iIterator, + final boolean iConvertToRecord) { + this.sourceRecord = iSourceRecord; + this.underlying = iIterator; + this.autoConvert2Record = iConvertToRecord; + this.source = null; + } + + public OLazyRecordIterator(final Iterable iSource, final boolean iConvertToRecord) { + this.sourceRecord = null; + this.autoConvert2Record = iConvertToRecord; + this.source = iSource; + this.underlying = iSource.iterator(); + } + + @SuppressWarnings("unchecked") + public OIdentifiable next() { + OIdentifiable value = underlying.next(); + + if (value == null) + return null; + + if (value instanceof ORecordId && autoConvert2Record && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) { + try { + final ORecord rec = ((ORecordId) value).getRecord(); + if (sourceRecord != null && rec != null) + ORecordInternal.track(sourceRecord, rec); + if (underlying instanceof OLazyIterator) + ((OLazyIterator) underlying).update(rec); + value = rec; + } catch (Exception e) { + OLogManager.instance().error(this, "Error on iterating record collection", e); + value = null; + } + + } + + return value; + } + + public boolean hasNext() { + return underlying.hasNext(); + } + + @SuppressWarnings("unchecked") + public OIdentifiable update(final OIdentifiable iValue) { + if (underlying instanceof OLazyIterator) { + final OIdentifiable old = ((OLazyIterator) underlying).update(iValue); + if (sourceRecord != null && !old.equals(iValue)) + sourceRecord.setDirty(); + return old; + } else + throw new UnsupportedOperationException("Underlying iterator not supports lazy updates (Interface OLazyIterator"); + } + + public void remove() { + underlying.remove(); + if (sourceRecord != null) + sourceRecord.setDirty(); + } + + @Override + public void reset() { + if (underlying instanceof OResettable) + ((OResettable) underlying).reset(); + else if (source != null) { + underlying = source.iterator(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordMultiIterator.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordMultiIterator.java new file mode 100644 index 00000000000..4d342f46312 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OLazyRecordMultiIterator.java @@ -0,0 +1,140 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.orientechnologies.common.collection.OLazyIterator; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Lazy implementation of Iterator that load the records only when accessed. It keep also track of changes to the source record + * avoiding to call setDirty() by hand. The main difference with OLazyRecordIterator is that this iterator handles multiple + * iterators of collections as they are just one. + * + * @see OLazyRecordIterator + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OLazyRecordMultiIterator implements OLazyIterator, OResettable { + final private ORecord sourceRecord; + final private Object[] underlyingSources; + final private Object[] underlyingIterators; + final private boolean convertToRecord; + private int iteratorIndex = 0; + + public OLazyRecordMultiIterator(final ORecord iSourceRecord, final Object[] iIterators, final boolean iConvertToRecord) { + this.sourceRecord = iSourceRecord; + this.underlyingSources = iIterators; + this.underlyingIterators = new Object[iIterators.length]; + this.convertToRecord = iConvertToRecord; + } + + @Override + public void reset() { + iteratorIndex = 0; + for (int i = 0; i < underlyingIterators.length; ++i) + underlyingIterators[i] = null; + } + + public OIdentifiable next() { + if (!hasNext()) + throw new NoSuchElementException(); + + final Iterator underlying = getCurrentIterator(); + OIdentifiable value = underlying.next(); + + if (value == null) + return null; + + if (value instanceof ORecordId && convertToRecord) { + value = ((ORecordId) value).getRecord(); + + if (underlying instanceof OLazyIterator) + ((OLazyIterator) underlying).update(value); + } + + return value; + } + + public boolean hasNext() { + final Iterator underlying = getCurrentIterator(); + boolean again = underlying.hasNext(); + + while (!again && iteratorIndex < underlyingIterators.length - 1) { + iteratorIndex++; + again = getCurrentIterator().hasNext(); + } + + return again; + } + + public OIdentifiable update(final OIdentifiable iValue) { + final Iterator underlying = getCurrentIterator(); + if (underlying instanceof OLazyIterator) { + final OIdentifiable old = ((OLazyIterator) underlying).update(iValue); + if (sourceRecord != null && !old.equals(iValue)) + sourceRecord.setDirty(); + return old; + } else + throw new UnsupportedOperationException("Underlying iterator not supports lazy updates (Interface OLazyIterator"); + } + + public void remove() { + final Iterator underlying = getCurrentIterator(); + underlying.remove(); + if (sourceRecord != null) + sourceRecord.setDirty(); + } + + @SuppressWarnings("unchecked") + private Iterator getCurrentIterator() { + if (iteratorIndex > underlyingIterators.length) + throw new NoSuchElementException(); + + Object next = underlyingIterators[iteratorIndex]; + if (next == null) { + // GET THE ITERATOR + if (underlyingSources[iteratorIndex] instanceof OResettable) { + // REUSE IT + ((OResettable) underlyingSources[iteratorIndex]).reset(); + underlyingIterators[iteratorIndex] = underlyingSources[iteratorIndex]; + } else if (underlyingSources[iteratorIndex] instanceof Iterable) { + // CREATE A NEW ONE FROM THE COLLECTION + underlyingIterators[iteratorIndex] = ((Iterable) underlyingSources[iteratorIndex]).iterator(); + } else if (underlyingSources[iteratorIndex] instanceof Iterator) { + // COPY IT + underlyingIterators[iteratorIndex] = underlyingSources[iteratorIndex]; + } else + throw new IllegalStateException("Unsupported iteration source: " + underlyingSources[iteratorIndex]); + + next = underlyingIterators[iteratorIndex]; + } + + if (next instanceof Iterator) + return (Iterator) next; + + return ((Collection) next).iterator(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeEvent.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeEvent.java new file mode 100644 index 00000000000..cfac5447c0f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeEvent.java @@ -0,0 +1,132 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +/** + * Event that contains information about operation that is performed on tracked collection. + * + * @param + * Value that indicates position of item inside collection. + * @param + * Item value. + */ +public class OMultiValueChangeEvent { + /** + * Operation that is performed on collection. + */ + public enum OChangeType { + ADD, UPDATE, REMOVE, NESTED + } + + /** + * Operation that is performed on collection. + */ + private final OChangeType changeType; + + /** + * Value that indicates position of item inside collection. + */ + private final K key; + + /** + * New item value. + */ + private V value; + + /** + * Previous item value. + */ + private V oldValue; + + private boolean changesOwnerContent = true; + + public OMultiValueChangeEvent(OChangeType changeType, K key, V value) { + this.changeType = changeType; + this.key = key; + this.value = value; + } + + public OMultiValueChangeEvent(OChangeType changeType, K key, V value, V oldValue) { + this.changeType = changeType; + this.key = key; + this.value = value; + this.oldValue = oldValue; + } + + public OMultiValueChangeEvent(OChangeType changeType, K key, V value, V oldValue, boolean changesOwnerContent) { + this.changeType = changeType; + this.key = key; + this.value = value; + this.oldValue = oldValue; + this.changesOwnerContent = changesOwnerContent; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public OChangeType getChangeType() { + return changeType; + } + + public V getOldValue() { + return oldValue; + } + + public boolean isChangesOwnerContent() { + return changesOwnerContent; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OMultiValueChangeEvent that = (OMultiValueChangeEvent) o; + + return changesOwnerContent == that.changesOwnerContent && changeType == that.changeType + && !(key != null ? !key.equals(that.key) : that.key != null) + && !(oldValue != null ? !oldValue.equals(that.oldValue) : that.oldValue != null) + && !(value != null ? !value.equals(that.value) : that.value != null); + + } + + @Override + public int hashCode() { + int result = changeType != null ? changeType.hashCode() : 0; + result = 31 * result + (key != null ? key.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (oldValue != null ? oldValue.hashCode() : 0); + result = 31 * result + (changesOwnerContent ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "OMultiValueChangeEvent{" + "changeType=" + changeType + ", key=" + key + ", value=" + value + ", oldValue=" + oldValue + + '}'; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeListener.java new file mode 100644 index 00000000000..bd14a282d06 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeListener.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +/** + * Listen operations that are performed on tracked collection. + * + * @param Value that indicates position of item inside collection. + * @param Item value. + */ +public interface OMultiValueChangeListener { + /** + * Called when operation on collection is completed. + * + * @param event Operation description. + */ + public void onAfterRecordChanged(OMultiValueChangeEvent event); +} + diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeTimeLine.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeTimeLine.java new file mode 100644 index 00000000000..8b93a8680ee --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OMultiValueChangeTimeLine.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Container that contains information about all operations that were performed on collection starting from + * the time when it was loaded from DB. + * + * @param Value that uniquely identifies position of element inside collection. + * @param Value that is hold by collection. + */ +public class OMultiValueChangeTimeLine { + private final List> multiValueChangeEvents = new ArrayList>(); + + /** + * @return List of all operations that were performed on collection starting from + * the time when it was loaded from DB. + */ + public List> getMultiValueChangeEvents() { + return Collections.unmodifiableList(multiValueChangeEvents); + } + + /** + * Add new operation that was performed on collection to collection history. + * + * @param changeEvent Description of operation that was performed on collection. + */ + public void addCollectionChangeEvent(OMultiValueChangeEvent changeEvent) { + multiValueChangeEvents.add(changeEvent); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ONestedValueChangeListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ONestedValueChangeListener.java new file mode 100644 index 00000000000..0799d42f4e6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ONestedValueChangeListener.java @@ -0,0 +1,48 @@ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeListener; +import com.orientechnologies.orient.core.db.record.OTrackedMultiValue; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ONestedMultiValueChangeEvent; + +import java.lang.ref.WeakReference; + +/** + * Created by tglman on 11/03/16. + */ +public class ONestedValueChangeListener implements OMultiValueChangeListener { + + private WeakReference ownerDoc; + private OTrackedMultiValue ownerCollection; + private OTrackedMultiValue currentCollecion; + private ONestedMultiValueChangeEvent nestedEvent; + + public ONestedValueChangeListener(ODocument ownerDoc, OTrackedMultiValue ownerCollection, OTrackedMultiValue currentCollecion) { + this.ownerDoc = new WeakReference(ownerDoc); + this.ownerCollection = ownerCollection; + this.currentCollecion = currentCollecion; + } + + @Override + public void onAfterRecordChanged(OMultiValueChangeEvent event) { + if (ownerDoc.get() == null) + return; + + if (nestedEvent == null) { + nestedEvent = new ONestedMultiValueChangeEvent(currentCollecion, currentCollecion); + ownerCollection.fireCollectionChangedEvent(nestedEvent); + + } + OMultiValueChangeTimeLine timeline = nestedEvent.getTimeLine(); + if (timeline == null) { + timeline = new OMultiValueChangeTimeLine(); + nestedEvent.setTimeLine(timeline); + } + timeline.addCollectionChangeEvent(event); + + } + + + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OPlaceholder.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OPlaceholder.java new file mode 100755 index 00000000000..27802159f94 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OPlaceholder.java @@ -0,0 +1,136 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.OStreamable; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * Base interface for identifiable objects. This abstraction is required to use ORID and ORecord in many points. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OPlaceholder implements OIdentifiable, OStreamable { + private ORecordId rid; + private int recordVersion; + + /** + * Empty constructor used by serialization + */ + public OPlaceholder() { + } + + public OPlaceholder(final ORecordId rid, final int version) { + this.rid = rid; + this.recordVersion = version; + } + + public OPlaceholder(final ORecord iRecord) { + rid = (ORecordId) iRecord.getIdentity().copy(); + recordVersion = iRecord.getVersion(); + } + + @Override + public ORID getIdentity() { + return rid; + } + + @Override + public T getRecord() { + return rid.getRecord(); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof OPlaceholder)) + return false; + + final OPlaceholder other = (OPlaceholder) obj; + + return rid.equals(other.rid) && recordVersion == other.recordVersion; + } + + @Override + public int hashCode() { + return rid.hashCode() + recordVersion; + } + + @Override + public int compareTo(OIdentifiable o) { + return rid.compareTo(o); + } + + @Override + public int compare(OIdentifiable o1, OIdentifiable o2) { + return rid.compare(o1, o2); + } + + public int getVersion() { + return recordVersion; + } + + @Override + public String toString() { + return rid.toString() + " v." + recordVersion; + } + + @Override + public void toStream(final DataOutput out) throws IOException { + rid.toStream(out); + out.writeInt(recordVersion); + } + + @Override + public void fromStream(final DataInput in) throws IOException { + rid = new ORecordId(); + rid.fromStream(in); + recordVersion = in.readInt(); + } + + @Override + public void lock(final boolean iExclusive) { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().lockRecord(this, + iExclusive ? OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK : OStorage.LOCKING_STRATEGY.SHARED_LOCK); + } + + @Override + public boolean isLocked() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().isLockedRecord(this); + } + + @Override + public OStorage.LOCKING_STRATEGY lockingStrategy() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().lockingStrategy(this); + } + + @Override + public void unlock() { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().unlockRecord(this); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OProxedResource.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OProxedResource.java new file mode 100644 index 00000000000..b05da2c85ac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OProxedResource.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; + +/** + * Generic proxy abstratc class. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OProxedResource { + protected final T delegate; + protected final ODatabaseDocumentInternal database; + + protected OProxedResource(final T iDelegate, final ODatabaseDocumentInternal iDatabase) { + this.delegate = iDelegate; + this.database = iDatabase; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordElement.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordElement.java new file mode 100755 index 00000000000..8e96d0a1d87 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordElement.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Base interface that represents a record element. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ORecordElement { + /** + * Available record statuses. + */ + public enum STATUS { + NOT_LOADED, LOADED, MARSHALLING, UNMARSHALLING + } + + /** + * Returns the current status of the record. + * + * @return Current status as value between the defined values in the enum {@link STATUS} + */ + public STATUS getInternalStatus(); + + /** + * Changes the current internal status. + * + * @param iStatus + * status between the values defined in the enum {@link STATUS} + */ + public void setInternalStatus(STATUS iStatus); + + /** + * Marks the instance as dirty. The dirty status could be propagated up if the implementation supports ownership concept. + * + * @return The object it self. Useful to call methods in chain. + */ + public RET setDirty(); + + void setDirtyNoChanged(); + + /** + * @return Returns record element which contains given one. + */ + public ORecordElement getOwner(); + + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyList.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyList.java new file mode 100644 index 00000000000..63837ebef45 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyList.java @@ -0,0 +1,550 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.orientechnologies.common.collection.OLazyIterator; +import com.orientechnologies.common.collection.OLazyIteratorListWrapper; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.ORecordMultiValueHelper.MULTIVALUE_CONTENT_TYPE; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +/** + * Lazy implementation of ArrayList. It's bound to a source ORecord object to keep track of changes. This avoid to call the + * makeDirty() by hand when the list is changed. It handles an internal contentType to speed up some operations like conversion + * to/from record/links. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings({ "serial" }) +public class ORecordLazyList extends ORecordTrackedList implements ORecordLazyMultiValue { + protected final byte recordType; + protected ORecordLazyListener listener; + protected ORecordMultiValueHelper.MULTIVALUE_CONTENT_TYPE contentType = MULTIVALUE_CONTENT_TYPE.EMPTY; + protected StringBuilder stream; + protected boolean autoConvertToRecord = true; + protected boolean marshalling = false; + protected boolean ridOnly = false; + + public ORecordLazyList() { + super(null); + this.recordType = ODocument.RECORD_TYPE; + } + + public ORecordLazyList(final ODocument iSourceRecord) { + super(iSourceRecord); + if (iSourceRecord != null) { + this.recordType = ORecordInternal.getRecordType(iSourceRecord); + if (!iSourceRecord.isLazyLoad()) + // SET AS NON-LAZY LOAD THE COLLECTION TOO + autoConvertToRecord = false; + } else + this.recordType = ODocument.RECORD_TYPE; + } + + public ORecordLazyList(final ODocument iSourceRecord, final Collection iOrigin) { + this(iSourceRecord); + if (iOrigin != null && !iOrigin.isEmpty()) + addAll(iOrigin); + } + + @SuppressWarnings("unchecked") + @Override + public boolean addAll(Collection c) { + final Iterator it = (Iterator) (c instanceof ORecordLazyMultiValue ? ((ORecordLazyMultiValue) c).rawIterator() : c.iterator()); + + while (it.hasNext()) { + Object o = it.next(); + if (o == null) + add(null); + else if (o instanceof OIdentifiable) + add((OIdentifiable) o); + else + OMultiValue.add(this, o); + } + + return true; + } + + @Override + public boolean isEmpty() { + if (stream == null) + return super.isEmpty(); + else + // AVOID TO LAZY LOAD IT, JUST CHECK IF STREAM IS EMPTY OR NULL + return stream.length() == 0; + } + + /** + * @return iterator that just returns the elements without convertion. + */ + public Iterator rawIterator() { + lazyLoad(false); + final Iterator subIterator = new OLazyIterator() { + private int pos = -1; + + public boolean hasNext() { + return pos < size() - 1; + } + + public OIdentifiable next() { + return ORecordLazyList.this.rawGet(++pos); + } + + public void remove() { + ORecordLazyList.this.remove(pos); + } + + public OIdentifiable update(final OIdentifiable iValue) { + return ORecordLazyList.this.set(pos, iValue); + } + }; + return new OLazyRecordIterator(sourceRecord, subIterator, false); + } + + public OIdentifiable rawGet(final int index) { + lazyLoad(false); + return super.get(index); + } + + @Override + public OLazyIterator iterator() { + lazyLoad(false); + return new OLazyRecordIterator(sourceRecord, new OLazyIteratorListWrapper(super.listIterator()), + autoConvertToRecord && getOwner().getInternalStatus() != STATUS.MARSHALLING); + } + + @Override + public ListIterator listIterator() { + lazyLoad(false); + return super.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + lazyLoad(false); + return super.listIterator(index); + } + + @Override + public boolean contains(final Object o) { + if (OGlobalConfiguration.LAZYSET_WORK_ON_STREAM.getValueAsBoolean() && getStreamedContent() != null) + return getStreamedContent().indexOf(((OIdentifiable) o).getIdentity().toString()) > -1; + + lazyLoad(false); + return super.contains(o); + } + + @Override + public boolean add(OIdentifiable e) { + if (e != null) { + if ((ridOnly || contentType == MULTIVALUE_CONTENT_TYPE.ALL_RIDS || OGlobalConfiguration.LAZYSET_WORK_ON_STREAM + .getValueAsBoolean()) && e.getIdentity().isPersistent() && (e instanceof ODocument && !((ODocument) e).isDirty())) + // IT'S BETTER TO LEAVE ALL RIDS AND EXTRACT ONLY THIS ONE + e = e.getIdentity(); + else + contentType = ORecordMultiValueHelper.updateContentType(contentType, e); + } + lazyLoad(true); + return super.add(e); + } + + @Override + public void add(int index, OIdentifiable e) { + if (e != null) { + ORecordInternal.track(sourceRecord, e); + if ((ridOnly || contentType == MULTIVALUE_CONTENT_TYPE.ALL_RIDS || OGlobalConfiguration.LAZYSET_WORK_ON_STREAM + .getValueAsBoolean()) && e.getIdentity().isPersistent() && (e instanceof ODocument && !((ODocument) e).isDirty())) + // IT'S BETTER TO LEAVE ALL RIDS AND EXTRACT ONLY THIS ONE + e = e.getIdentity(); + else + contentType = ORecordMultiValueHelper.updateContentType(contentType, e); + } + lazyLoad(true); + + super.add(index, e); + } + + @Override + public OIdentifiable set(int index, OIdentifiable e) { + lazyLoad(true); + + if (e != null) { + ORecordInternal.track(sourceRecord, e); + if (e != null) + if ((ridOnly || contentType == MULTIVALUE_CONTENT_TYPE.ALL_RIDS || OGlobalConfiguration.LAZYSET_WORK_ON_STREAM + .getValueAsBoolean()) && e.getIdentity().isPersistent() && (e instanceof ODocument && !((ODocument) e).isDirty())) + // IT'S BETTER TO LEAVE ALL RIDS AND EXTRACT ONLY THIS ONE + e = e.getIdentity(); + else + contentType = ORecordMultiValueHelper.updateContentType(contentType, e); + } + return super.set(index, e); + } + + @Override + public OIdentifiable get(final int index) { + lazyLoad(false); + if (autoConvertToRecord) + convertLink2Record(index); + return super.get(index); + } + + @Override + public int indexOf(final Object o) { + lazyLoad(false); + return super.indexOf(o); + } + + @Override + public int lastIndexOf(final Object o) { + lazyLoad(false); + return super.lastIndexOf(o); + } + + @Override + public OIdentifiable remove(final int iIndex) { + lazyLoad(true); + return super.remove(iIndex); + } + + @Override + public boolean remove(final Object iElement) { + if (iElement == null) { + return clearDeletedRecords(); + } + final boolean result; + if (OGlobalConfiguration.LAZYSET_WORK_ON_STREAM.getValueAsBoolean() && getStreamedContent() != null) { + // WORK ON STREAM + final StringBuilder stream = getStreamedContent(); + final String rid = ((OIdentifiable) iElement).getIdentity().toString(); + int pos = stream.indexOf(rid); + if (pos > -1) { + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, + pos, null, (OIdentifiable) iElement)); + + // FOUND: REMOVE IT DIRECTLY FROM STREAM + if (pos > 0) + pos--; + stream.delete(pos, pos + rid.length() + 1); + if (stream.length() == 0) + setStreamedContent(null); + result = true; + } else + result = false; + } else { + lazyLoad(true); + result = super.remove(iElement); + } + + if (isEmpty()) + contentType = MULTIVALUE_CONTENT_TYPE.EMPTY; + + return result; + } + + @Override + public void clear() { + lazyLoad(true); + super.clear(); + contentType = MULTIVALUE_CONTENT_TYPE.EMPTY; + stream = null; + } + + @Override + public int size() { + lazyLoad(false); + return super.size(); + } + + @SuppressWarnings("unchecked") + @Override + public RET setDirty() { + if (!marshalling) + return (RET) super.setDirty(); + return (RET) this; + } + + @Override + public void setDirtyNoChanged() { + if (!marshalling) + super.setDirtyNoChanged(); + } + + @Override + public Object[] toArray() { + convertLinks2Records(); + return super.toArray(); + } + + @Override + public T[] toArray(final T[] a) { + lazyLoad(false); + convertLinks2Records(); + return super.toArray(a); + } + + public void convertLinks2Records() { + lazyLoad(false); + if (contentType == MULTIVALUE_CONTENT_TYPE.ALL_RECORDS || !autoConvertToRecord) + // PRECONDITIONS + return; + + for (int i = 0; i < size(); ++i) { + try { + convertLink2Record(i); + } catch (ORecordNotFoundException e) { + // LEAVE THE RID DIRTY + } + } + + contentType = MULTIVALUE_CONTENT_TYPE.ALL_RECORDS; + } + + public boolean convertRecords2Links() { + if (contentType == MULTIVALUE_CONTENT_TYPE.ALL_RIDS || sourceRecord == null) + // PRECONDITIONS + return true; + + boolean allConverted = true; + for (int i = 0; i < super.size(); ++i) { + try { + if (!convertRecord2Link(i)) + allConverted = false; + } catch (ORecordNotFoundException e) { + // LEAVE THE RID DIRTY + } + } + + if (allConverted) + contentType = MULTIVALUE_CONTENT_TYPE.ALL_RIDS; + + return allConverted; + } + + public boolean isAutoConvertToRecord() { + return autoConvertToRecord; + } + + public void setAutoConvertToRecord(boolean convertToDocument) { + this.autoConvertToRecord = convertToDocument; + } + + @Override + public String toString() { + if (stream == null) + return ORecordMultiValueHelper.toString(this); + else { + return "[NOT LOADED: " + stream + ']'; + } + } + + public byte getRecordType() { + return recordType; + } + + public ORecordLazyList copy(final ODocument iSourceRecord) { + final ORecordLazyList copy = new ORecordLazyList(iSourceRecord); + copy.contentType = contentType; + copy.stream = stream; + copy.autoConvertToRecord = autoConvertToRecord; + + final int tot = super.size(); + for (int i = 0; i < tot; ++i) + copy.add(rawGet(i)); + + return copy; + } + + public Iterator newItemsIterator() { + return null; + } + + public StringBuilder getStreamedContent() { + return stream; + } + + public ORecordLazyList setStreamedContent(final StringBuilder iStream) { + if (iStream == null || iStream.length() == 0) + stream = null; + else { + // CREATE A COPY TO FREE ORIGINAL BUFFER + stream = iStream; + final int prevModCount = modCount; + reset(); + modCount = prevModCount; + } + + contentType = MULTIVALUE_CONTENT_TYPE.ALL_RIDS; + return this; + } + + public ORecordLazyListener getListener() { + return listener; + } + + public ORecordLazyList setListener(final ORecordLazyListener listener) { + this.listener = listener; + return this; + } + + public boolean lazyLoad(final boolean iInvalidateStream) { + if (stream == null) + return false; + + marshalling = true; + int currentModCount = modCount; + final List items = OStringSerializerHelper.smartSplit(stream.toString(), OStringSerializerHelper.RECORD_SEPARATOR); + + for (String item : items) { + if (item.length() == 0) + super.add(new ORecordId()); + else + super.add(new ORecordId(item)); + } + + modCount = currentModCount; + marshalling = false; + + // if (iInvalidateStream) + stream = null; + contentType = MULTIVALUE_CONTENT_TYPE.ALL_RIDS; + + if (listener != null) + listener.onLazyLoad(); + + return true; + } + + public boolean isRidOnly() { + return ridOnly; + } + + public ORecordLazyList setRidOnly(boolean ridOnly) { + this.ridOnly = ridOnly; + return this; + } + + public boolean detach() { + return convertRecords2Links(); + } + + @Override + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (!marshalling) + super.fireCollectionChangedEvent(event); + } + + /** + * Convert the item requested from link to record. + * + * @param iIndex + * Position of the item to convert + */ + private void convertLink2Record(final int iIndex) { + if (ridOnly || !autoConvertToRecord) + // PRECONDITIONS + return; + + final OIdentifiable o = super.get(iIndex); + + if (contentType == MULTIVALUE_CONTENT_TYPE.ALL_RECORDS && !o.getIdentity().isNew()) + // ALL RECORDS AND THE OBJECT IS NOT NEW, DO NOTHING + return; + + if (o != null && o instanceof ORecordId) { + final ORecordId rid = (ORecordId) o; + + marshalling = true; + try { + ORecord record = rid.getRecord(); + if (record != null) { + ORecordInternal.unTrack(sourceRecord, rid); + ORecordInternal.track(sourceRecord, record); + } + super.set(iIndex, record); + + } catch (ORecordNotFoundException e) { + // IGNORE THIS + } finally { + marshalling = false; + } + } + } + + /** + * Convert the item requested from record to link. + * + * @param iIndex + * Position of the item to convert + * @return true if conversion was successful. + */ + private boolean convertRecord2Link(final int iIndex) { + if (contentType == MULTIVALUE_CONTENT_TYPE.ALL_RIDS) + // PRECONDITIONS + return true; + + final Object o = super.get(iIndex); + + if (o != null && o instanceof OIdentifiable && ((OIdentifiable) o).getIdentity().isPersistent()) { + if (o instanceof ORecord && !((ORecord) o).isDirty()) { + marshalling = true; + try { + super.set(iIndex, ((ORecord) o).getIdentity()); + // CONVERTED + return true; + } catch (ORecordNotFoundException e) { + // IGNORE THIS + } finally { + marshalling = false; + } + } else if (o instanceof ORID) + // ALREADY CONVERTED + return true; + } + return false; + } + + public boolean clearDeletedRecords() { + boolean removed = false; + Iterator it = super.iterator(); + while (it.hasNext()) { + OIdentifiable rec = it.next(); + if (!(rec instanceof ORecord) && rec.getRecord() == null) { + it.remove(); + removed = true; + } + } + return removed; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyListener.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyListener.java new file mode 100644 index 00000000000..6db3fecdb12 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyListener.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +/** + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface ORecordLazyListener { + public void onLazyLoad(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMap.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMap.java new file mode 100755 index 00000000000..2d0b0dade26 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMap.java @@ -0,0 +1,257 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.ORecordMultiValueHelper.MULTIVALUE_CONTENT_TYPE; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * Lazy implementation of LinkedHashMap. It's bound to a source ORecord object to keep track of changes. This avoid to call the + * makeDirty() by hand when the map is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings({ "serial" }) +public class ORecordLazyMap extends OTrackedMap implements ORecordLazyMultiValue { + final private byte recordType; + private ORecordMultiValueHelper.MULTIVALUE_CONTENT_TYPE status = MULTIVALUE_CONTENT_TYPE.EMPTY; + protected boolean marshalling = false; + private boolean autoConvertToRecord = true; + + public ORecordLazyMap(final ODocument iSourceRecord) { + super(iSourceRecord); + this.recordType = ODocument.RECORD_TYPE; + } + + public ORecordLazyMap(final ODocument iSourceRecord, final byte iRecordType) { + super(iSourceRecord); + this.recordType = iRecordType; + + if (iSourceRecord != null) { + if (!iSourceRecord.isLazyLoad()) + // SET AS NON-LAZY LOAD THE COLLECTION TOO + autoConvertToRecord = false; + } + } + + public ORecordLazyMap(final ODocument iSourceRecord, final Map iOrigin) { + this(iSourceRecord); + if (iOrigin != null && !iOrigin.isEmpty()) + putAll(iOrigin); + } + + @Override + public boolean containsValue(final Object o) { + return super.containsValue(o); + } + + @Override + public OIdentifiable get(final Object iKey) { + if (iKey == null) + return null; + + final String key = iKey.toString(); + + if (autoConvertToRecord) + convertLink2Record(key); + + return super.get(key); + } + + @Override + public OIdentifiable put(final Object key, OIdentifiable value) { + if (status == MULTIVALUE_CONTENT_TYPE.ALL_RIDS && value instanceof ORecord && !value.getIdentity().isNew()) + // IT'S BETTER TO LEAVE ALL RIDS AND EXTRACT ONLY THIS ONE + value = value.getIdentity(); + else + status = ORecordMultiValueHelper.updateContentType(status, value); + + return super.put(key, value); + } + + @Override + public Collection values() { + convertLinks2Records(); + return super.values(); + } + + @Override + public OIdentifiable remove(Object o) { + final OIdentifiable result = super.remove(o); + if (size() == 0) + status = MULTIVALUE_CONTENT_TYPE.EMPTY; + return result; + } + + @Override + public void clear() { + super.clear(); + status = MULTIVALUE_CONTENT_TYPE.EMPTY; + } + + @Override + public String toString() { + return ORecordMultiValueHelper.toString(this); + } + + public boolean isAutoConvertToRecord() { + return autoConvertToRecord; + } + + public void setAutoConvertToRecord(boolean convertToRecord) { + this.autoConvertToRecord = convertToRecord; + } + + public void convertLinks2Records() { + if (status == MULTIVALUE_CONTENT_TYPE.ALL_RECORDS || !autoConvertToRecord + || getOwner().getInternalStatus() == STATUS.MARSHALLING) + // PRECONDITIONS + return; + for (Object k : keySet()) + convertLink2Record(k); + + status = MULTIVALUE_CONTENT_TYPE.ALL_RECORDS; + } + + public boolean convertRecords2Links() { + if (status == MULTIVALUE_CONTENT_TYPE.ALL_RIDS) + // PRECONDITIONS + return true; + + boolean allConverted = true; + for (Object k : keySet()) + if (!convertRecord2Link(k)) + allConverted = false; + + if (allConverted) + status = MULTIVALUE_CONTENT_TYPE.ALL_RIDS; + + return allConverted; + } + + private boolean convertRecord2Link(final Object iKey) { + if (status == MULTIVALUE_CONTENT_TYPE.ALL_RIDS) + return true; + + final Object value = super.get(iKey); + if (value != null) + if (value instanceof ORecord && !((ORecord) value).getIdentity().isNew()) { + marshalling = true; + try { + // OVERWRITE + super.put(iKey, ((ORecord) value).getIdentity()); + } finally { + marshalling = false; + } + + // CONVERTED + return true; + } else if (value instanceof ORID) + // ALREADY CONVERTED + return true; + + return false; + } + + /** + * Convert the item with the received key to a record. + * + * @param iKey + * Key of the item to convert + */ + private void convertLink2Record(final Object iKey) { + if (status == MULTIVALUE_CONTENT_TYPE.ALL_RECORDS) + return; + + final Object value; + + if (iKey instanceof ORID) + value = iKey; + else + value = super.get(iKey); + + if (value != null && value instanceof ORID) { + final ORID rid = (ORID) value; + marshalling = true; + try { + try { + // OVERWRITE IT + ORecord record = rid.getRecord(); + if(record != null){ + ORecordInternal.unTrack(sourceRecord, rid); + ORecordInternal.track(sourceRecord, record); + } + super.put(iKey, record); + } catch (ORecordNotFoundException e) { + // IGNORE THIS + } + } finally { + marshalling = false; + } + } + } + + @Override + public OTrackedMap setDirty() { + if (!marshalling) + return super.setDirty(); + + return this; + } + + @Override + public void setDirtyNoChanged() { + if (!marshalling) + super.setDirtyNoChanged(); + } + + @Override + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (!marshalling) + super.fireCollectionChangedEvent(event); + } + + public byte getRecordType() { + return recordType; + } + + public Iterator rawIterator() { + return new OLazyRecordIterator(sourceRecord, super.values().iterator(), false); + } + + public boolean detach() { + return convertRecords2Links(); + } + + @Override + public int size() { + return super.size(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMultiValue.java new file mode 100644 index 00000000000..c1796cb4d65 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazyMultiValue.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.common.util.OSizeable; + +import java.util.Iterator; + +public interface ORecordLazyMultiValue extends OAutoConvertToRecord, ODetachable, OSizeable { + Iterator rawIterator(); + + /** + * Browse all the set to convert all the items into records. + * + * It converts only items that already loaded into memory from storage. To convert records that will be fetched from disk later + * use {@link #setAutoConvertToRecord(boolean)} + */ + void convertLinks2Records(); + + /** + * Browse all the set to convert all the items into links. + * + * @return + */ + boolean convertRecords2Links(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazySet.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazySet.java new file mode 100755 index 00000000000..c01ebd476c0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordLazySet.java @@ -0,0 +1,246 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; + +import com.orientechnologies.common.collection.OLazyIterator; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.record.OIdentityChangeListener; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Lazy implementation of Set. Can be bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() + * by hand when the set is changed. + *

      + * Internals: + *

        + *
      • stores new records in a separate IdentityHashMap to keep underlying list (delegate) always ordered and minimizing sort + * operations
      • + *
      • + *
      + * + *

      + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORecordLazySet extends ORecordTrackedSet implements Set, ORecordLazyMultiValue, ORecordElement, + OIdentityChangeListener { + protected boolean autoConvertToRecord = true; + + public ORecordLazySet(final ODocument iSourceRecord) { + super(iSourceRecord); + } + + public ORecordLazySet(ODocument iSourceRecord, Collection iOrigin) { + this(iSourceRecord); + if (iOrigin != null && !iOrigin.isEmpty()) + addAll(iOrigin); + } + + @Override + public boolean detach() { + return convertRecords2Links(); + } + + @Override + public Iterator iterator() { + return new OLazyRecordIterator(new OLazyIterator() { + { + iter = ORecordLazySet.super.map.entrySet().iterator(); + } + private Iterator> iter; + private Entry last; + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public OIdentifiable next() { + Entry entry = iter.next(); + last = entry; + if (entry.getValue() != ENTRY_REMOVAL) + return (OIdentifiable) entry.getValue(); + return entry.getKey(); + } + + @Override + public void remove() { + iter.remove(); + if (last.getKey() instanceof ORecord) + ORecordInternal.removeIdentityChangeListener((ORecord) last.getKey(), ORecordLazySet.this); + } + + @Override + public OIdentifiable update(OIdentifiable iValue) { + if (iValue != null) + map.put(iValue.getIdentity(), iValue.getRecord()); + return iValue; + } + }, autoConvertToRecord && getOwner().getInternalStatus() != STATUS.MARSHALLING); + } + + @Override + public Iterator rawIterator() { + return new OLazyRecordIterator(super.iterator(), false); + } + + @Override + public boolean add(OIdentifiable e) { + if (map.containsKey(e)) + return false; + + if (e == null) + map.put(null, null); + else if (e instanceof ORecord && e.getIdentity().isNew()) { + ORecordInternal.addIdentityChangeListener((ORecord) e, this); + ORecordInternal.track(sourceRecord, e); + map.put(e, e); + } else if (!e.getIdentity().isPersistent()) { + // record id is not fixed yet, so we need to be able to watch for id changes, so get the record for this id to be able to do + // this. + final ORecord record = e.getRecord(); + if (record == null) + throw new IllegalArgumentException("Record with id " + e.getIdentity() + " has not be found"); + ORecordInternal.addIdentityChangeListener(record, this); + ORecordInternal.track(sourceRecord, e); + map.put(e, record); + } else + map.put(e, ENTRY_REMOVAL); + setDirty(); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, e, + e)); + + return true; + } + + public void convertLinks2Records() { + final Iterator> all = map.entrySet().iterator(); + while (all.hasNext()) { + Entry entry = all.next(); + if (entry.getValue() == ENTRY_REMOVAL) { + try { + ORecord record = entry.getKey().getRecord(); + if (record != null) { + ORecordInternal.unTrack(sourceRecord, entry.getKey()); + ORecordInternal.track(sourceRecord, record); + } + entry.setValue(record); + } catch (ORecordNotFoundException e) { + // IGNORE THIS + } + } + } + + } + + @Override + public void onAfterIdentityChange(ORecord record) { + map.put(record, record); + } + + @Override + public void onBeforeIdentityChange(ORecord record) { + map.remove(record); + } + + @Override + public boolean convertRecords2Links() { + return true; + } + + public boolean clearDeletedRecords() { + boolean removed = false; + final Iterator> all = map.entrySet().iterator(); + while (all.hasNext()) { + Entry entry = all.next(); + if (entry.getValue() == ENTRY_REMOVAL) { + try { + if (entry.getKey().getRecord() == null) { + all.remove(); + removed = true; + } + } catch (ORecordNotFoundException e) { + all.remove(); + removed = true; + } + } + } + return removed; + } + + public boolean remove(Object o) { + if (o == null) + return clearDeletedRecords(); + + final Object old = map.remove(o); + if (old != null) { + if (o instanceof ORecord) + ORecordInternal.removeIdentityChangeListener((ORecord) o, this); + + setDirty(); + fireCollectionChangedEvent(new OMultiValueChangeEvent( + OMultiValueChangeEvent.OChangeType.REMOVE, (OIdentifiable) o, null, (OIdentifiable) o)); + return true; + } + return false; + } + + @Override + public boolean isAutoConvertToRecord() { + return autoConvertToRecord; + } + + @Override + public void setAutoConvertToRecord(boolean convertToRecord) { + this.autoConvertToRecord = convertToRecord; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Set) { + Set coll = ((Set) obj); + if (map.size() == coll.size()) { + for (Object obje : coll) { + if (!map.containsKey(obje)) + return false; + } + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return map.hashCode(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordMultiValueHelper.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordMultiValueHelper.java new file mode 100644 index 00000000000..16cb11ddf06 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordMultiValueHelper.java @@ -0,0 +1,70 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Lazy implementation of ArrayList. It's bound to a source ORecord object to keep track of changes. This avoid to call the + * makeDirty() by hand when the list is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORecordMultiValueHelper { + public enum MULTIVALUE_CONTENT_TYPE { + EMPTY, ALL_RECORDS, ALL_RIDS, HYBRID + } + + public static MULTIVALUE_CONTENT_TYPE updateContentType(final MULTIVALUE_CONTENT_TYPE iPreviousStatus, final Object iValue) { + if (iPreviousStatus == MULTIVALUE_CONTENT_TYPE.HYBRID) { + // DO NOTHING + + } else if (iPreviousStatus == MULTIVALUE_CONTENT_TYPE.EMPTY) { + if (iValue instanceof ORID) + return MULTIVALUE_CONTENT_TYPE.ALL_RIDS; + else if (iValue instanceof ORecord) + return MULTIVALUE_CONTENT_TYPE.ALL_RECORDS; + else + return MULTIVALUE_CONTENT_TYPE.HYBRID; + + } else if (iPreviousStatus == MULTIVALUE_CONTENT_TYPE.ALL_RECORDS) { + if (iValue instanceof ORID) + return MULTIVALUE_CONTENT_TYPE.HYBRID; + + } else if (iPreviousStatus == MULTIVALUE_CONTENT_TYPE.ALL_RIDS) { + if (!(iValue instanceof ORID)) + return MULTIVALUE_CONTENT_TYPE.HYBRID; + } + return iPreviousStatus; + } + + public static String toString(final ORecordLazyMultiValue iMultivalue) { + final boolean previousAutoConvertSetting = iMultivalue.isAutoConvertToRecord(); + iMultivalue.setAutoConvertToRecord(false); + + final String result = OMultiValue.toString(iMultivalue); + + iMultivalue.setAutoConvertToRecord(previousAutoConvertSetting); + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordOperation.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordOperation.java new file mode 100755 index 00000000000..3e415771cd3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordOperation.java @@ -0,0 +1,126 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; + +import java.util.Locale; + +/** + * Contains the information about a database operation. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ORecordOperation implements Comparable { + + public static final byte LOADED = 0; + public static final byte UPDATED = 1; + public static final byte DELETED = 2; + public static final byte CREATED = 3; + public static final byte RECYCLED = 4; + + public byte type; + public OIdentifiable record; + + public ORecordOperation() { + } + + public ORecordOperation(final OIdentifiable iRecord, final byte iStatus) { + // CLONE RECORD AND CONTENT + this.record = iRecord; + this.type = iStatus; + } + + @Override + public int hashCode() { + return record.getIdentity().hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof ORecordOperation)) + return false; + + return record.equals(((ORecordOperation) obj).record); + } + + @Override + public String toString() { + return new StringBuilder(128).append("ORecordOperation [record=").append(record).append(", type=").append(getName(type)) + .append("]").toString(); + } + + public OIdentifiable setRecord(final OIdentifiable record) { + this.record = record; + return record; + } + + public ORecord getRecord() { + return record != null ? record.getRecord() : null; + } + + public ORID getRID() { + return record != null ? record.getIdentity() : null; + } + + public static String getName(final int type) { + String operation = "?"; + switch (type) { + case ORecordOperation.CREATED: + operation = "CREATE"; + break; + case ORecordOperation.UPDATED: + operation = "UPDATE"; + break; + case ORecordOperation.DELETED: + operation = "DELETE"; + break; + case ORecordOperation.LOADED: + operation = "READ"; + break; + case ORecordOperation.RECYCLED: + operation = "RECYCLED"; + break; + } + return operation; + } + + public static byte getId(String iName) { + iName = iName.toUpperCase(Locale.ENGLISH); + + if (iName.startsWith("CREAT")) + return ORecordOperation.CREATED; + else if (iName.startsWith("UPDAT")) + return ORecordOperation.UPDATED; + else if (iName.startsWith("DELET")) + return ORecordOperation.DELETED; + else if (iName.startsWith("READ")) + return ORecordOperation.LOADED; + else if (iName.startsWith("RECYCLED")) + return ORecordOperation.RECYCLED; + return -1; + } + + @Override + public int compareTo(Object o) { + return record.compareTo(((ORecordOperation) o).record); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedIterator.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedIterator.java new file mode 100644 index 00000000000..b14492d331c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedIterator.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Iterator; + +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Implementation of Iterator that keeps track of changes to the source record avoiding to call setDirty() by hand. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORecordTrackedIterator implements Iterator { + final private ORecord sourceRecord; + final private Iterator underlying; + + public ORecordTrackedIterator(final ORecord iSourceRecord, final Iterator iIterator) { + this.sourceRecord = iSourceRecord; + this.underlying = iIterator; + } + + public OIdentifiable next() { + return (OIdentifiable) underlying.next(); + } + + public boolean hasNext() { + return underlying.hasNext(); + } + + public void remove() { + underlying.remove(); + if (sourceRecord != null) + sourceRecord.setDirty(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedList.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedList.java new file mode 100644 index 00000000000..05a2319c06b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedList.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Iterator; + +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Implementation of ArrayList bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() by hand + * when the list is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings({ "serial" }) +public class ORecordTrackedList extends OTrackedList { + public ORecordTrackedList(final ORecord iSourceRecord) { + super(iSourceRecord); + } + + public Iterator rawIterator() { + return iterator(); + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + //not needed do nothing + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedSet.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedSet.java new file mode 100755 index 00000000000..10bece8d1c5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ORecordTrackedSet.java @@ -0,0 +1,219 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.*; + +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Implementation of Set bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() by hand when + * the set is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORecordTrackedSet extends AbstractCollection implements Set, + OTrackedMultiValue, ORecordElement { + protected final ORecord sourceRecord; + protected Map map = new HashMap(); + private STATUS status = STATUS.NOT_LOADED; + protected final static Object ENTRY_REMOVAL = new Object(); + private List> changeListeners; + + public ORecordTrackedSet(final ORecord iSourceRecord) { + this.sourceRecord = iSourceRecord; + if (iSourceRecord != null) + iSourceRecord.setDirty(); + } + + @Override + public ORecordElement getOwner() { + return sourceRecord; + } + + public Iterator iterator() { + return new ORecordTrackedIterator(sourceRecord, map.keySet().iterator()); + } + + public boolean add(final OIdentifiable e) { + if (map.containsKey(e)) + return false; + + map.put(e, ENTRY_REMOVAL); + setDirty(); + + ORecordInternal.track(sourceRecord, e); + + if (e instanceof ODocument) + ODocumentInternal.addOwner((ODocument) e, this); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, e, + e)); + return true; + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + public boolean remove(Object o) { + final Object old = map.remove(o); + if (old != null) { + if (o instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) o, this); + + setDirty(); + fireCollectionChangedEvent(new OMultiValueChangeEvent( + OMultiValueChangeEvent.OChangeType.REMOVE, (OIdentifiable) o, null, (OIdentifiable) o)); + return true; + } + return false; + } + + public void clear() { + setDirty(); + map.clear(); + } + + public boolean removeAll(final Collection c) { + boolean changed = false; + for (Object item : c) { + if (remove(item)) + changed = true; + } + + if (changed) + setDirty(); + + return changed; + } + + public boolean addAll(final Collection c) { + if (c == null || c.size() == 0) + return false; + + for (OIdentifiable o : c) + add(o); + + setDirty(); + return true; + } + + public boolean retainAll(final Collection c) { + if (c == null || c.size() == 0) + return false; + + if (super.retainAll(c)) { + setDirty(); + return true; + } + return false; + } + + @Override + public int size() { + return map.size(); + } + + @SuppressWarnings("unchecked") + public ORecordTrackedSet setDirty() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null + && !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord))) + sourceRecord.setDirty(); + return this; + } + + @Override + public void setDirtyNoChanged() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null) + sourceRecord.setDirtyNoChanged(); + } + + public STATUS getInternalStatus() { + return status; + } + + public void setInternalStatus(final STATUS iStatus) { + status = iStatus; + } + + public void addChangeListener(final OMultiValueChangeListener changeListener) { + if(changeListeners == null) + changeListeners = new LinkedList>(); + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners != null) + changeListeners.remove(changeListener); + } + + public Set returnOriginalState(final List> events) { + final Set reverted = new HashSet(this); + + final ListIterator> listIterator = events.listIterator(events.size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey()); + break; + case REMOVE: + reverted.add(event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (getOwner().getInternalStatus() == STATUS.UNMARSHALLING) + return; + + setDirty(); + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + @Override + public Class getGenericClass() { + return OIdentifiable.class; + } + + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + //not needed do nothing + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedList.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedList.java new file mode 100755 index 00000000000..65c79f482ae --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedList.java @@ -0,0 +1,296 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +import java.io.Serializable; +import java.util.*; + +/** + * Implementation of ArrayList bound to a source ORecord object to keep track of changes for literal types. This avoid to call the + * makeDirty() by hand when the list is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings({ "serial" }) +public class OTrackedList extends ArrayList implements ORecordElement, OTrackedMultiValue, Serializable { + protected final ORecord sourceRecord; + private STATUS status = STATUS.NOT_LOADED; + protected List> changeListeners = null; + protected Class genericClass; + private final boolean embeddedCollection; + + public OTrackedList(final ORecord iRecord, final Collection iOrigin, final Class iGenericClass) { + this(iRecord); + genericClass = iGenericClass; + if (iOrigin != null && !iOrigin.isEmpty()) + addAll(iOrigin); + } + + public OTrackedList(final ORecord iSourceRecord) { + this.sourceRecord = iSourceRecord; + embeddedCollection = this.getClass().equals(OTrackedList.class); + } + + @Override + public ORecordElement getOwner() { + return sourceRecord; + } + + @Override + public boolean add(T element) { + final boolean result = super.add(element); + + if (result) { + addOwnerToEmbeddedDoc(element); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, super.size() - 1, + element)); + } + + addNested(element); + return result; + } + + @Override + public boolean addAll(final Collection c) { + for (T o : c) { + add(o); + } + return true; + } + + @Override + public void add(int index, T element) { + super.add(index, element); + + addOwnerToEmbeddedDoc(element); + addNested(element); + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, index, element)); + } + + @Override + public T set(int index, T element) { + final T oldValue = super.set(index, element); + + if (oldValue != null && !oldValue.equals(element)) { + if (oldValue instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) oldValue, this); + + addOwnerToEmbeddedDoc(element); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.UPDATE, index, element, + oldValue)); + } + + addNested(element); + + return oldValue; + } + + private void addNested(T element) { + if (element instanceof OTrackedMultiValue) { + ((OTrackedMultiValue) element) + .addChangeListener(new ONestedValueChangeListener((ODocument) sourceRecord, this, (OTrackedMultiValue) element)); + } + } + + private void addOwnerToEmbeddedDoc(T e) { + if (embeddedCollection && e instanceof ODocument && !((ODocument) e).getIdentity().isValid()) { + ODocumentInternal.addOwner((ODocument) e, this); + } + if (e instanceof ODocument) + ORecordInternal.track(sourceRecord, (ODocument) e); + } + + @Override + public T remove(int index) { + final T oldValue = super.remove(index); + if (oldValue instanceof ODocument) { + ODocumentInternal.removeOwner((ODocument) oldValue, this); + } + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, index, null, + oldValue)); + removeNested(oldValue); + + return oldValue; + } + + private void removeNested(Object element){ + if(element instanceof OTrackedMultiValue){ +// ((OTrackedMultiValue) element).removeRecordChangeListener(null); + } + } + + @Override + public boolean remove(Object o) { + final int index = indexOf(o); + if (index >= 0) { + remove(index); + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + boolean removed = false; + for (Object o : c) + removed = removed | remove(o); + + return removed; + } + + @Override + public void clear() { + final List origValues; + + if (changeListeners!=null && changeListeners.isEmpty()) + origValues = null; + else + origValues = new ArrayList(this); + + if (origValues == null) { + for (final T item : this) { + if (item instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) item, this); + } + } + + super.clear(); + if (origValues != null) + for (int i = origValues.size() - 1; i >= 0; i--) { + final T origValue = origValues.get(i); + + if (origValue instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) origValue, this); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, i, null, + origValue)); + removeNested(origValue); + } + else + setDirty(); + } + + public void reset() { + super.clear(); + } + + @SuppressWarnings("unchecked") + public RET setDirty() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null + && !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord))) + sourceRecord.setDirty(); + return (RET) this; + } + + @Override + public void setDirtyNoChanged() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null) + sourceRecord.setDirtyNoChanged(); + } + + public void addChangeListener(final OMultiValueChangeListener changeListener) { + if(changeListeners==null){ + changeListeners = new LinkedList>(); + } + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + if(changeListeners!=null) { + changeListeners.remove(changeListener); + } + } + + public List returnOriginalState(final List> multiValueChangeEvents) { + final List reverted = new ArrayList(this); + + final ListIterator> listIterator = multiValueChangeEvents + .listIterator(multiValueChangeEvents.size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey().intValue()); + break; + case REMOVE: + reverted.add(event.getKey(), event.getOldValue()); + break; + case UPDATE: + reverted.set(event.getKey(), event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (status == STATUS.UNMARSHALLING) + return; + setDirty(); + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + public STATUS getInternalStatus() { + return status; + } + + public void setInternalStatus(final STATUS iStatus) { + status = iStatus; + } + + public Class getGenericClass() { + return genericClass; + } + + public void setGenericClass(Class genericClass) { + this.genericClass = genericClass; + } + + private Object writeReplace() { + return new ArrayList(this); + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + super.set((Integer) event.getKey(), (T) newValue); + addNested((T) newValue); + } + + + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMap.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMap.java new file mode 100755 index 00000000000..c079be06b2c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMap.java @@ -0,0 +1,252 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.io.Serializable; +import java.util.*; + +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Implementation of LinkedHashMap bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() by + * hand when the map is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("serial") +public class OTrackedMap extends LinkedHashMap implements ORecordElement, OTrackedMultiValue, Serializable { + final protected ORecord sourceRecord; + private STATUS status = STATUS.NOT_LOADED; + private List> changeListeners = null; + protected Class genericClass; + private final boolean embeddedCollection; + + public OTrackedMap(final ORecord iRecord, final Map iOrigin, final Class cls) { + this(iRecord); + genericClass = cls; + if (iOrigin != null && !iOrigin.isEmpty()) + putAll(iOrigin); + } + + public OTrackedMap(final ORecord iSourceRecord) { + this.sourceRecord = iSourceRecord; + embeddedCollection = this.getClass().equals(OTrackedMap.class); + } + + @Override + public ORecordElement getOwner() { + return sourceRecord; + } + + @Override + public T put(final Object key, final T value) { + if (key == null) + throw new IllegalArgumentException("null key not supported by embedded map"); + boolean containsKey = containsKey(key); + + T oldValue = super.put(key, value); + + if (containsKey && oldValue == value) + return oldValue; + + if (oldValue instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) oldValue, this); + + addOwnerToEmbeddedDoc(value); + + if (containsKey) + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.UPDATE, key, value, + oldValue)); + else + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, key, value)); + addNested(value); + return oldValue; + } + + private void addNested(T element) { + if (element instanceof OTrackedMultiValue) { + ((OTrackedMultiValue) element) + .addChangeListener(new ONestedValueChangeListener((ODocument) sourceRecord, this, (OTrackedMultiValue) element)); + } + } + + private void addOwnerToEmbeddedDoc(T e) { + if (embeddedCollection && e instanceof ODocument && !((ODocument) e).getIdentity().isValid()) + ODocumentInternal.addOwner((ODocument) e, this); + if (e instanceof ODocument) + ORecordInternal.track(sourceRecord, (ODocument) e); + } + + @Override + public T remove(final Object iKey) { + boolean containsKey = containsKey(iKey); + final T oldValue = super.remove(iKey); + + if (oldValue instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) oldValue, this); + + if (containsKey) { + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, iKey, null, oldValue)); + removeNested(oldValue); + } + + + + return oldValue; + } + + @Override + public void clear() { + final Map origValues; + if (changeListeners == null) + origValues = null; + else + origValues = new HashMap(this); + + if (origValues == null) { + for (T value : super.values()) + if (value instanceof ODocument) { + ODocumentInternal.removeOwner((ODocument) value, this); + } + } + + super.clear(); + + if (origValues != null) { + for (Map.Entry entry : origValues.entrySet()) { + if (entry.getValue() instanceof ODocument) { + ODocumentInternal.removeOwner((ODocument) entry.getValue(), this); + } + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, entry.getKey(), + null, entry.getValue())); + removeNested(entry.getValue()); + } + } else + setDirty(); + } + + private void removeNested(Object element){ + if(element instanceof OTrackedMultiValue){ + // ((OTrackedMultiValue) element).removeRecordChangeListener(null); + } + } + + @Override + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings({ "unchecked" }) + public OTrackedMap setDirty() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null + && !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord))) + sourceRecord.setDirty(); + return this; + } + + @Override + public void setDirtyNoChanged() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null) + sourceRecord.setDirtyNoChanged(); + } + + public STATUS getInternalStatus() { + return status; + } + + public void setInternalStatus(final STATUS iStatus) { + status = iStatus; + } + + public void addChangeListener(OMultiValueChangeListener changeListener) { + if(changeListeners == null) + changeListeners = new LinkedList>(); + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(OMultiValueChangeListener changeListener) { + if (changeListeners != null) + changeListeners.remove(changeListener); + } + + public Map returnOriginalState(final List> multiValueChangeEvents) { + final Map reverted = new HashMap(this); + + final ListIterator> listIterator = multiValueChangeEvents.listIterator(multiValueChangeEvents + .size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey()); + break; + case REMOVE: + reverted.put(event.getKey(), event.getOldValue()); + break; + case UPDATE: + reverted.put(event.getKey(), event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (status == STATUS.UNMARSHALLING) + return; + + setDirty(); + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + public Class getGenericClass() { + return genericClass; + } + + public void setGenericClass(Class genericClass) { + this.genericClass = genericClass; + } + + private Object writeReplace() { + return new LinkedHashMap(this); + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + super.put(event.getKey(), (T) newValue); + addNested((T) newValue); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMultiValue.java new file mode 100644 index 00000000000..7fdd16f0d07 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedMultiValue.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.util.Collection; +import java.util.List; + +/** + * Interface that indicates that collection will send notifications about operations that are performed on it to the listeners. + * + * @param + * Value that indicates position of item inside collection. + * @param + * Value that is hold by collection. + */ +public interface OTrackedMultiValue { + + /** + * Add change listener. + * + * @param changeListener + * Change listener instance. + */ + void addChangeListener(OMultiValueChangeListener changeListener); + + /** + * Remove change listener. + * + * @param changeListener + * Change listener instance. + */ + void removeRecordChangeListener(OMultiValueChangeListener changeListener); + + /** + * + * Reverts all operations that were performed on collection and return original collection state. + * + * @param changeEvents + * List of operations that were performed on collection. + * + * @return Original collection state. + */ + Object returnOriginalState(List> changeEvents); + + void fireCollectionChangedEvent(final OMultiValueChangeEvent event); + + Class getGenericClass(); + + void replace(OMultiValueChangeEvent event, Object newValue); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedSet.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedSet.java new file mode 100755 index 00000000000..d03b0b8616a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/OTrackedSet.java @@ -0,0 +1,250 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record; + +import java.io.Serializable; +import java.util.*; + +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Implementation of Set bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() by hand when + * the set is changed. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("serial") +public class OTrackedSet extends HashSet implements ORecordElement, OTrackedMultiValue, Serializable { + protected final ORecord sourceRecord; + private final boolean embeddedCollection; + protected Class genericClass; + private STATUS status = STATUS.NOT_LOADED; + private List> changeListeners; + + public OTrackedSet(final ORecord iRecord, final Collection iOrigin, final Class cls) { + this(iRecord); + genericClass = cls; + if (iOrigin != null && !iOrigin.isEmpty()) + addAll(iOrigin); + } + + public OTrackedSet(final ORecord iSourceRecord) { + this.sourceRecord = iSourceRecord; + embeddedCollection = this.getClass().equals(OTrackedSet.class); + } + + @Override + public ORecordElement getOwner() { + return sourceRecord; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator underlying = OTrackedSet.super.iterator(); + + @Override + public boolean hasNext() { + return underlying.hasNext(); + } + + @Override + public T next() { + return underlying.next(); + } + + @Override + public void remove() { + underlying.remove(); + setDirty(); + } + }; + } + + public boolean add(final T e) { + if (super.add(e)) { + addOwnerToEmbeddedDoc(e); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, e, e)); + addNested(e); + return true; + } + + return false; + } + + private void addNested(T element) { + if (element instanceof OTrackedMultiValue) { + ((OTrackedMultiValue) element) + .addChangeListener(new ONestedValueChangeListener((ODocument) sourceRecord, this, (OTrackedMultiValue) element)); + } + } + + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object o) { + if (super.remove(o)) { + if (o instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) o, this); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, (T) o, null, (T) o)); + removeNested(o); + return true; + } + return false; + } + + @Override + public void clear() { + final Set origValues; + if (changeListeners == null ) + origValues = null; + else + origValues = new HashSet(this); + + if (origValues == null) { + for (final T item : this) { + if (item instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) item, this); + } + } + + super.clear(); + + if (origValues != null) { + for (final T item : origValues) { + if (item instanceof ODocument) + ODocumentInternal.removeOwner((ODocument) item, this); + + fireCollectionChangedEvent(new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, item, null, item)); + removeNested(item); + } + + } else + setDirty(); + } + + + private void removeNested(Object element){ + if(element instanceof OTrackedMultiValue){ + // ((OTrackedMultiValue) element).removeRecordChangeListener(null); + } + } + + @SuppressWarnings("unchecked") + public OTrackedSet setDirty() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null + && !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord))) + sourceRecord.setDirty(); + return this; + } + + @Override + public void setDirtyNoChanged() { + if (status != STATUS.UNMARSHALLING && sourceRecord != null) + sourceRecord.setDirtyNoChanged(); + } + + public STATUS getInternalStatus() { + return status; + } + + public void setInternalStatus(final STATUS iStatus) { + status = iStatus; + } + + public void addChangeListener(final OMultiValueChangeListener changeListener) { + if(changeListeners == null) + changeListeners = new LinkedList>(); + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners != null) + changeListeners.remove(changeListener); + } + + public Set returnOriginalState(final List> multiValueChangeEvents) { + final Set reverted = new HashSet(this); + + final ListIterator> listIterator = multiValueChangeEvents.listIterator(multiValueChangeEvents + .size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey()); + break; + case REMOVE: + reverted.add(event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + public Class getGenericClass() { + return genericClass; + } + + public void setGenericClass(Class genericClass) { + this.genericClass = genericClass; + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (status == STATUS.UNMARSHALLING) + return; + + setDirty(); + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + private void addOwnerToEmbeddedDoc(T e) { + if (embeddedCollection && e instanceof ODocument && !((ODocument) e).getIdentity().isValid()) { + ODocumentInternal.addOwner((ODocument) e, this); + ORecordInternal.track(sourceRecord, (ODocument) e); + } + } + + private Object writeReplace() { + return new HashSet(this); + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + super.remove(event.getKey()); + super.add((T) newValue); + addNested((T) newValue); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/DoubleReferenceItem.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/DoubleReferenceItem.java new file mode 100644 index 00000000000..eddd3e129e3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/DoubleReferenceItem.java @@ -0,0 +1,41 @@ +package com.orientechnologies.orient.core.db.record.ridbag; + +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; + +public class DoubleReferenceItem { + private ORidBag ridBagOne; + private OBonsaiBucketPointer oBonsaiBucketPointer; + private ORidBag ridBagTwo; + private String fieldNameOne; + private String fieldNameTwo; + + public DoubleReferenceItem(String fieldNameOne, ORidBag ridBagOne, String fieldNameTwo, ORidBag ridBagTwo, + OBonsaiBucketPointer oBonsaiBucketPointer) { + this.ridBagOne = ridBagOne; + this.oBonsaiBucketPointer = oBonsaiBucketPointer; + this.ridBagTwo = ridBagTwo; + this.fieldNameOne = fieldNameOne; + this.fieldNameTwo = fieldNameTwo; + } + + public String getFieldNameOne() { + return fieldNameOne; + } + + public String getFieldNameTwo() { + return fieldNameTwo; + } + + public OBonsaiBucketPointer getoBonsaiBucketPointer() { + return oBonsaiBucketPointer; + } + + public ORidBag getRidBagOne() { + return ridBagOne; + } + + public ORidBag getRidBagTwo() { + return ridBagTwo; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBag.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBag.java new file mode 100755 index 00000000000..a874a2a5fea --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBag.java @@ -0,0 +1,465 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag; + +import com.orientechnologies.common.collection.OCollection; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OUUIDSerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.embedded.OEmbeddedRidBag; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeCollectionManager; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeRidBag; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.BytesContainer; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringBuilderSerializable; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +/** + * A collection that contain links to {@link OIdentifiable}. Bag is similar to set but can contain several entering of the same + * object.
      + *

      + * Could be tree based and embedded representation.
      + *

        + *
      • + * Embedded stores its content directly to the document that owns it.
        + * It better fits for cases when only small amount of links are stored to the bag.
        + *
      • + *
      • + * Tree-based implementation stores its content in a separate data structure called {@link OSBTreeBonsai}.
        + * It fits great for cases when you have a huge amount of links.
        + *
      • + *
      + *
      + * The representation is automatically converted to tree-based implementation when top threshold is reached. And backward to + * embedded one when size is decreased to bottom threshold.
      + * The thresholds could be configured by {@link OGlobalConfiguration#RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD} and + * {@link OGlobalConfiguration#RID_BAG_SBTREEBONSAI_TO_EMBEDDED_THRESHOLD}.
      + *
      + * This collection is used to efficiently manage relationships in graph model.
      + *
      + * Does not implement {@link Collection} interface because some operations could not be efficiently implemented and that's why + * should be avoided.
      + * + * @author Artem Orobets (enisher-at-gmail.com) + * @author Andrey Lomakin + * @since 1.7rc1 + */ +public class ORidBag implements OStringBuilderSerializable, Iterable, ORecordLazyMultiValue, + OTrackedMultiValue, OCollection { + private ORidBagDelegate delegate; + + private int topThreshold = OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.getValueAsInteger(); + private int bottomThreshold = OGlobalConfiguration.RID_BAG_SBTREEBONSAI_TO_EMBEDDED_THRESHOLD.getValueAsInteger(); + + private UUID uuid; + + public ORidBag(final ORidBag ridBag) { + init(); + for (Iterator it = ridBag.rawIterator(); it.hasNext();) + add(it.next()); + } + + public ORidBag() { + init(); + } + + public ORidBag(final int iTopThreshold, final int iBottomThreshold) { + topThreshold = iTopThreshold; + bottomThreshold = iBottomThreshold; + init(); + } + + private ORidBag(final byte[] stream) { + fromStream(stream); + } + + public static ORidBag fromStream(final String value) { + final byte[] stream = OBase64Utils.decode(value); + return new ORidBag(stream); + } + + public ORidBag copy() { + final ORidBag copy = new ORidBag(); + copy.topThreshold = topThreshold; + copy.bottomThreshold = bottomThreshold; + copy.uuid = uuid; + + if (delegate instanceof OSBTreeRidBag) + // ALREADY MULTI-THREAD + copy.delegate = delegate; + else + copy.delegate = ((OEmbeddedRidBag) delegate).copy(); + + return copy; + } + + /** + * THIS IS VERY EXPENSIVE METHOD AND CAN NOT BE CALLED IN REMOTE STORAGE. + * + * @param identifiable Object to check. + * + * @return true if ridbag contains at leas one instance with the same rid as passed in identifiable. + */ + public boolean contains(OIdentifiable identifiable) { + return delegate.contains(identifiable); + } + + public void addAll(Collection values) { + delegate.addAll(values); + } + + public void add(OIdentifiable identifiable) { + delegate.add(identifiable); + } + + public void remove(OIdentifiable identifiable) { + delegate.remove(identifiable); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public Iterator rawIterator() { + return delegate.rawIterator(); + } + + @Override + public void convertLinks2Records() { + delegate.convertLinks2Records(); + } + + @Override + public boolean convertRecords2Links() { + return delegate.convertRecords2Links(); + } + + @Override + public boolean isAutoConvertToRecord() { + return delegate.isAutoConvertToRecord(); + } + + @Override + public void setAutoConvertToRecord(boolean convertToRecord) { + delegate.setAutoConvertToRecord(convertToRecord); + } + + @Override + public boolean detach() { + return delegate.detach(); + } + + @Override + public int size() { + return delegate.size(); + } + + public boolean isEmbedded() { + return delegate instanceof OEmbeddedRidBag; + } + + public int toStream(BytesContainer bytesContainer) throws OSerializationException { + + final ORecordSerializationContext context = ORecordSerializationContext.getContext(); + if (context != null) { + if (isEmbedded() && ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager() != null + && delegate.size() >= topThreshold) { + ORidBagDelegate oldDelegate = delegate; + delegate = new OSBTreeRidBag(); + boolean oldAutoConvert = oldDelegate.isAutoConvertToRecord(); + oldDelegate.setAutoConvertToRecord(false); + + for (OIdentifiable identifiable : oldDelegate) + delegate.add(identifiable); + + final ORecord owner = oldDelegate.getOwner(); + delegate.setOwner(owner); + + for (OMultiValueChangeListener listener : oldDelegate.getChangeListeners()) + delegate.addChangeListener(listener); + + owner.setDirty(); + + oldDelegate.setAutoConvertToRecord(oldAutoConvert); + oldDelegate.requestDelete(); + } else if (bottomThreshold >= 0 && !isEmbedded() && delegate.size() <= bottomThreshold) { + ORidBagDelegate oldDelegate = delegate; + boolean oldAutoConvert = oldDelegate.isAutoConvertToRecord(); + oldDelegate.setAutoConvertToRecord(false); + delegate = new OEmbeddedRidBag(); + + for (OIdentifiable identifiable : oldDelegate) + delegate.add(identifiable); + + final ORecord owner = oldDelegate.getOwner(); + delegate.setOwner(owner); + + for (OMultiValueChangeListener listener : oldDelegate.getChangeListeners()) + delegate.addChangeListener(listener); + + owner.setDirty(); + + oldDelegate.setAutoConvertToRecord(oldAutoConvert); + oldDelegate.requestDelete(); + } + } + + final UUID oldUuid = uuid; + final OSBTreeCollectionManager sbTreeCollectionManager = ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager(); + if (sbTreeCollectionManager != null) + uuid = sbTreeCollectionManager.listenForChanges(this); + else + uuid = null; + + boolean hasUuid = uuid != null; + + final int serializedSize = + OByteSerializer.BYTE_SIZE + delegate.getSerializedSize() + ((hasUuid) ? OUUIDSerializer.UUID_SIZE : 0); + int pointer = bytesContainer.alloc(serializedSize); + int offset = pointer; + final byte[] stream = bytesContainer.bytes; + + byte configByte = 0; + if (isEmbedded()) + configByte |= 1; + + if (hasUuid) + configByte |= 2; + + stream[offset++] = configByte; + + if (hasUuid) { + OUUIDSerializer.INSTANCE.serialize(uuid, stream, offset); + offset += OUUIDSerializer.UUID_SIZE; + } + + delegate.serialize(stream, offset, oldUuid); + return pointer; + } + + @Override + public OStringBuilderSerializable toStream(StringBuilder output) throws OSerializationException { + final BytesContainer container = new BytesContainer(); + toStream(container); + output.append(OBase64Utils.encodeBytes(container.bytes, 0, container.offset)); + return this; + } + + @Override + public String toString() { + return delegate.toString(); + } + + public void delete() { + delegate.requestDelete(); + } + + @Override + public OStringBuilderSerializable fromStream(StringBuilder input) throws OSerializationException { + final byte[] stream = OBase64Utils.decode(input.toString()); + fromStream(stream); + return this; + } + + public void fromStream(final byte[] stream) { + fromStream(new BytesContainer(stream)); + } + + public void fromStream(BytesContainer stream) { + final byte first = stream.bytes[stream.offset++]; + if ((first & 1) == 1) + delegate = new OEmbeddedRidBag(); + else + delegate = new OSBTreeRidBag(); + + if ((first & 2) == 2) { + uuid = OUUIDSerializer.INSTANCE.deserialize(stream.bytes, stream.offset); + stream.skip(OUUIDSerializer.UUID_SIZE); + } + + stream.skip(delegate.deserialize(stream.bytes, stream.offset) - stream.offset); + } + + @Override + public void addChangeListener(final OMultiValueChangeListener changeListener) { + delegate.addChangeListener(changeListener); + } + + @Override + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + delegate.removeRecordChangeListener(changeListener); + } + + @Override + public Object returnOriginalState(List> multiValueChangeEvents) { + return delegate.returnOriginalState(multiValueChangeEvents); + } + + @Override + public Class getGenericClass() { + return delegate.getGenericClass(); + } + + public void setOwner(ORecord owner) { + delegate.setOwner(owner); + } + + /** + * Temporary id of collection to track changes in remote mode. + *

      + * WARNING! Method is for internal usage. + * + * @return UUID + */ + public UUID getTemporaryId() { + return uuid; + } + + /** + * Notify collection that changes has been saved. Converts to non embedded implementation if needed. + *

      + * WARNING! Method is for internal usage. + * + * @param newPointer new collection pointer + */ + public void notifySaved(OBonsaiCollectionPointer newPointer) { + if (newPointer.isValid()) { + if (isEmbedded()) { + replaceWithSBTree(newPointer); + } else { + ((OSBTreeRidBag) delegate).setCollectionPointer(newPointer); + ((OSBTreeRidBag) delegate).clearChanges(); + } + } + } + + public OBonsaiCollectionPointer getPointer() { + if (isEmbedded()) { + return OBonsaiCollectionPointer.INVALID; + } else { + return ((OSBTreeRidBag) delegate).getCollectionPointer(); + } + } + + /** + * IMPORTANT! Only for internal usage. + */ + public boolean tryMerge(final ORidBag otherValue, boolean iMergeSingleItemsOfMultiValueFields) { + if (!isEmbedded() && !otherValue.isEmbedded()) { + final OSBTreeRidBag thisTree = (OSBTreeRidBag) delegate; + final OSBTreeRidBag otherTree = (OSBTreeRidBag) otherValue.delegate; + if (thisTree.getCollectionPointer().equals(otherTree.getCollectionPointer())) { + + thisTree.mergeChanges(otherTree); + + uuid = otherValue.uuid; + + return true; + } + } else if (iMergeSingleItemsOfMultiValueFields) { + final Iterator iter = otherValue.rawIterator(); + while (iter.hasNext()) { + final OIdentifiable value = iter.next(); + if (value != null) { + final Iterator localIter = rawIterator(); + boolean found = false; + while (localIter.hasNext()) { + final OIdentifiable v = localIter.next(); + if (value.equals(v)) { + found = true; + break; + } + } + if (!found) + add(value); + } + } + return true; + } + return false; + } + + protected void init() { + if (topThreshold < 0) + delegate = new OSBTreeRidBag(); + else + delegate = new OEmbeddedRidBag(); + } + + /** + * Silently replace delegate by tree implementation. + * + * @param pointer new collection pointer + */ + private void replaceWithSBTree(OBonsaiCollectionPointer pointer) { + delegate.requestDelete(); + final OSBTreeRidBag treeBag = new OSBTreeRidBag(); + treeBag.setCollectionPointer(pointer); + treeBag.setOwner(delegate.getOwner()); + for (OMultiValueChangeListener listener : delegate.getChangeListeners()) + treeBag.addChangeListener(listener); + delegate = treeBag; + } + + public void debugPrint(PrintStream writer) throws IOException { + if (delegate instanceof OSBTreeRidBag) { + writer.append("tree [\n"); + ((OSBTreeRidBag) delegate).debugPrint(writer); + writer.append("]\n"); + } else { + writer.append(delegate.toString()); + writer.append("\n"); + } + } + + protected ORidBagDelegate getDelegate() { + return delegate; + } + + @Override + public void fireCollectionChangedEvent(OMultiValueChangeEvent event) { + delegate.fireCollectionChangedEvent(event); + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + //not needed do nothing + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBagDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBagDelegate.java new file mode 100755 index 00000000000..083acd5eb79 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/ORidBagDelegate.java @@ -0,0 +1,82 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeListener; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.OTrackedMultiValue; +import com.orientechnologies.orient.core.record.ORecord; + +public interface ORidBagDelegate extends Iterable, ORecordLazyMultiValue, + OTrackedMultiValue { + void addAll(Collection values); + + void add(OIdentifiable identifiable); + + void remove(OIdentifiable identifiable); + + boolean isEmpty(); + + int getSerializedSize(); + + int getSerializedSize(byte[] stream, int offset); + + /** + * Writes content of bag to stream. + * + * OwnerUuid is needed to notify db about changes of collection pointer if some happens during serialization. + * + * @param stream + * to write content + * @param offset + * in stream where start to write content + * @param ownerUuid + * id of delegate owner + * @return offset where content of stream is ended + */ + int serialize(byte[] stream, int offset, UUID ownerUuid); + + int deserialize(byte[] stream, int offset); + + void requestDelete(); + + /** + * THIS IS VERY EXPENSIVE METHOD AND CAN NOT BE CALLED IN REMOTE STORAGE. + * + * @param identifiable + * Object to check. + * @return true if ridbag contains at leas one instance with the same rid as passed in identifiable. + */ + boolean contains(OIdentifiable identifiable); + + public void setOwner(ORecord owner); + + public ORecord getOwner(); + + public String toString(); + + public List> getChangeListeners(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/embedded/OEmbeddedRidBag.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/embedded/OEmbeddedRidBag.java new file mode 100755 index 00000000000..526e89d733c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/embedded/OEmbeddedRidBag.java @@ -0,0 +1,519 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.record.ridbag.embedded; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeListener; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBagDelegate; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; + +import java.util.*; + +public class OEmbeddedRidBag implements ORidBagDelegate { + private boolean contentWasChanged = false; + + private Object[] entries = OCommonConst.EMPTY_OBJECT_ARRAY; + private int entriesLength = 0; + + private boolean convertToRecord = true; + private int size = 0; + + private transient ORecord owner; + + private List> changeListeners; + + private static enum Tombstone { + TOMBSTONE + } + + private final class EntriesIterator implements Iterator, OResettable, OSizeable { + private final boolean convertToRecord; + private int currentIndex = -1; + private int nextIndex = -1; + private boolean currentRemoved; + + private EntriesIterator(boolean convertToRecord) { + reset(); + this.convertToRecord = convertToRecord; + } + + @Override + public boolean hasNext() { + //we may remove items in ridbag during iteration so we need to be sure that pointed item is not removed. + if (nextIndex > -1) { + if (entries[nextIndex] instanceof OIdentifiable) + return true; + + nextIndex = nextIndex(); + } + + return nextIndex > -1; + } + + @Override + public OIdentifiable next() { + currentRemoved = false; + + currentIndex = nextIndex; + if (currentIndex == -1) + throw new NoSuchElementException(); + + Object nextValue = entries[currentIndex]; + + //we may remove items in ridbag during iteration so we need to be sure that pointed item is not removed. + if (!(nextValue instanceof OIdentifiable)) { + nextIndex = nextIndex(); + + currentIndex = nextIndex; + if (currentIndex == -1) + throw new NoSuchElementException(); + + nextValue = entries[currentIndex]; + } + + nextIndex = nextIndex(); + + final OIdentifiable identifiable = (OIdentifiable) nextValue; + if (convertToRecord && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) + return identifiable.getRecord(); + + return identifiable; + } + + @Override + public void remove() { + if (currentRemoved) + throw new IllegalStateException("Current element has already been removed"); + + if (currentIndex == -1) + throw new IllegalStateException("Next method was not called for given iterator"); + + currentRemoved = true; + + final OIdentifiable nextValue = (OIdentifiable) entries[currentIndex]; + entries[currentIndex] = Tombstone.TOMBSTONE; + + size--; + contentWasChanged = true; + if (OEmbeddedRidBag.this.owner != null) + ORecordInternal.unTrack(OEmbeddedRidBag.this.owner, nextValue); + + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, nextValue, null, + nextValue)); + } + + @Override + public void reset() { + currentIndex = -1; + nextIndex = -1; + currentRemoved = false; + + nextIndex = nextIndex(); + } + + @Override + public int size() { + return size; + } + + private int nextIndex() { + for (int i = currentIndex + 1; i < entriesLength; i++) { + Object entry = entries[i]; + if (entry instanceof OIdentifiable) + return i; + } + + return -1; + } + } + + @Override + public ORecord getOwner() { + return owner; + } + + @Override + public boolean contains(OIdentifiable identifiable) { + if (identifiable == null) + return false; + + for (int i = 0; i < entriesLength; i++) { + if (identifiable.equals(entries[i])) + return true; + } + + return false; + } + + @Override + public void setOwner(ORecord owner) { + if (owner != null && this.owner != null && !this.owner.equals(owner)) { + throw new IllegalStateException("This data structure is owned by document " + owner + + " if you want to use it in other document create new rid bag instance and copy content of current one."); + } + if (this.owner != null) { + for (int i = 0; i < entriesLength; i++) { + final Object entry = entries[i]; + if (entry instanceof OIdentifiable) { + ORecordInternal.unTrack(this.owner, (OIdentifiable) entry); + } + } + } + + this.owner = owner; + if (this.owner != null) { + for (int i = 0; i < entriesLength; i++) { + final Object entry = entries[i]; + if (entry instanceof OIdentifiable) { + ORecordInternal.track(this.owner, (OIdentifiable) entry); + } + } + } + } + + @Override + public void addAll(Collection values) { + for (OIdentifiable value : values) + add(value); + } + + @Override + public void add(final OIdentifiable identifiable) { + if (identifiable == null) + throw new IllegalArgumentException("Impossible to add a null identifiable in a ridbag"); + + addEntry(identifiable); + + size++; + contentWasChanged = true; + + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, identifiable, + identifiable)); + } + + public OEmbeddedRidBag copy() { + final OEmbeddedRidBag copy = new OEmbeddedRidBag(); + copy.contentWasChanged = contentWasChanged; + copy.entries = entries; + copy.entriesLength = entriesLength; + copy.convertToRecord = convertToRecord; + copy.size = size; + copy.owner = owner; + if (changeListeners != null) { + copy.changeListeners = new LinkedList>(changeListeners); + } + return copy; + } + + @Override + public void remove(OIdentifiable identifiable) { + + if (removeEntry(identifiable)) { + size--; + contentWasChanged = true; + + if (this.owner != null) + ORecordInternal.unTrack(this.owner, identifiable); + + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, identifiable, null, + identifiable)); + } + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public Iterator iterator() { + return new EntriesIterator(convertToRecord); + } + + @Override + public Iterator rawIterator() { + return new EntriesIterator(false); + } + + @Override + public void convertLinks2Records() { + for (int i = 0; i < entriesLength; i++) { + final Object entry = entries[i]; + + if (entry instanceof OIdentifiable) { + final OIdentifiable identifiable = (OIdentifiable) entry; + ORecord record = identifiable.getRecord(); + if (record != null) { + if (this.owner != null) { + ORecordInternal.unTrack(this.owner, identifiable); + ORecordInternal.track(this.owner, record); + } + entries[i] = record; + } + } + } + } + + @Override + public boolean convertRecords2Links() { + for (int i = 0; i < entriesLength; i++) { + final Object entry = entries[i]; + + if (entry instanceof OIdentifiable) { + final OIdentifiable identifiable = (OIdentifiable) entry; + if (identifiable instanceof ORecord) { + final ORecord record = (ORecord) identifiable; + + entries[i] = record.getIdentity(); + } + } + } + + return true; + } + + @Override + public boolean isAutoConvertToRecord() { + return convertToRecord; + } + + @Override + public void setAutoConvertToRecord(boolean convertToRecord) { + this.convertToRecord = convertToRecord; + } + + @Override + public boolean detach() { + return convertRecords2Links(); + } + + @Override + public int size() { + return size; + } + + @Override + public String toString() { + if (size < 10) { + final StringBuilder sb = new StringBuilder(256); + sb.append('['); + for (final Iterator it = this.rawIterator(); it.hasNext(); ) { + try { + OIdentifiable e = it.next(); + if (e != null) { + if (sb.length() > 1) + sb.append(", "); + + sb.append(e.getIdentity()); + } + } catch (NoSuchElementException ex) { + // IGNORE THIS + } + } + return sb.append(']').toString(); + } else + return "[size=" + size + "]"; + } + + public void addChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners == null) + changeListeners = new LinkedList>(); + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners != null) + changeListeners.remove(changeListener); + } + + @Override + public Object returnOriginalState(List> multiValueChangeEvents) { + final OEmbeddedRidBag reverted = new OEmbeddedRidBag(); + for (OIdentifiable identifiable : this) + reverted.add(identifiable); + + final ListIterator> listIterator = multiValueChangeEvents + .listIterator(multiValueChangeEvents.size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey()); + break; + case REMOVE: + reverted.add(event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + @Override + public int getSerializedSize() { + int size; + + size = OIntegerSerializer.INT_SIZE; + + size += this.size * OLinkSerializer.RID_SIZE; + + return size; + } + + @Override + public int getSerializedSize(byte[] stream, int offset) { + return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset) * OLinkSerializer.RID_SIZE + OIntegerSerializer.INT_SIZE; + } + + @Override + public int serialize(byte[] stream, int offset, UUID ownerUuid) { + OIntegerSerializer.INSTANCE.serializeLiteral(size, stream, offset); + offset += OIntegerSerializer.INT_SIZE; + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + final int totEntries = entries.length; + for (int i = 0; i < totEntries; ++i) { + final Object entry = entries[i]; + if (entry instanceof OIdentifiable) { + OIdentifiable link = (OIdentifiable) entry; + final ORID rid = link.getIdentity(); + if (db != null && db.getTransaction().isActive()) { + if (!link.getIdentity().isPersistent()) { + link = db.getTransaction().getRecord(link.getIdentity()); + entries[i] = link; + } + } + + if (link == null) + throw new OSerializationException("Found null entry in ridbag with rid=" + rid); + + OLinkSerializer.INSTANCE.serialize(link, stream, offset); + offset += OLinkSerializer.RID_SIZE; + } + } + + return offset; + } + + @Override + public int deserialize(final byte[] stream, int offset) { + this.size = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset); + int entriesSize = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + for (int i = 0; i < entriesSize; i++) { + ORID rid = OLinkSerializer.INSTANCE.deserialize(stream, offset); + offset += OLinkSerializer.RID_SIZE; + + OIdentifiable identifiable = null; + if (rid.isTemporary()) + identifiable = rid.getRecord(); + + if (identifiable == null) + identifiable = rid; + + if (identifiable == null) + OLogManager.instance().warn(this, "Found null reference during ridbag deserialization (rid=%s)", rid); + else + addEntry(identifiable); + } + + return offset; + } + + @Override + public void requestDelete() { + } + + @Override + public Class getGenericClass() { + return OIdentifiable.class; + } + + @Override + public List> getChangeListeners() { + if (changeListeners == null) + return Collections.emptyList(); + return Collections.unmodifiableList(changeListeners); + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + private void addEntry(final OIdentifiable identifiable) { + if (entries.length == entriesLength) { + if (entriesLength == 0) { + final int cfgValue = OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.getValueAsInteger(); + entries = new Object[cfgValue > 0 ? Math.min(cfgValue, 40) : 40]; + } else { + final Object[] oldEntries = entries; + entries = new Object[entries.length << 1]; + System.arraycopy(oldEntries, 0, entries, 0, oldEntries.length); + } + } + if (this.owner != null) + ORecordInternal.track(this.owner, identifiable); + + entries[entriesLength] = identifiable; + entriesLength++; + } + + private boolean removeEntry(OIdentifiable identifiable) { + int i = 0; + for (; i < entriesLength; i++) { + final Object entry = entries[i]; + if (entry.equals(identifiable)) { + entries[i] = Tombstone.TOMBSTONE; + break; + } + } + + return i < entriesLength; + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + // do nothing not needed + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OBonsaiCollectionPointer.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OBonsaiCollectionPointer.java new file mode 100755 index 00000000000..b90e6eb909d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OBonsaiCollectionPointer.java @@ -0,0 +1,90 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; + +import java.io.Serializable; + +/** + * The pointer to a bonsai collection. + * + * Determines where the collection is stored. Contains file id and pointer to the root bucket. Is immutable. + * + * @author Artem Orobets (enisher-at-gmail.com) + * @see ORidBag + * @since 1.7rc1 + */ +public class OBonsaiCollectionPointer implements Serializable { + private static final long serialVersionUID = 1; + + public static final OBonsaiCollectionPointer INVALID = new OBonsaiCollectionPointer(-1, new OBonsaiBucketPointer(-1, -1)); + + private final long fileId; + private final OBonsaiBucketPointer rootPointer; + + public OBonsaiCollectionPointer(long fileId, OBonsaiBucketPointer rootPointer) { + this.fileId = fileId; + this.rootPointer = rootPointer; + } + + public long getFileId() { + return fileId; + } + + public OBonsaiBucketPointer getRootPointer() { + return rootPointer; + } + + public boolean isValid() { + return fileId >= 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OBonsaiCollectionPointer that = (OBonsaiCollectionPointer) o; + + if (fileId != that.fileId) + return false; + if (!rootPointer.equals(that.rootPointer)) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (fileId ^ (fileId >>> 32)); + result = 31 * result + rootPointer.hashCode(); + return result; + } + + @Override + public String toString() { + return "OBonsaiCollectionPointer{" + "fileId=" + fileId + ", rootPointer=" + rootPointer + '}'; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainer.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainer.java new file mode 100755 index 00000000000..40e7698bfb2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainer.java @@ -0,0 +1,255 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.OIndexEngineException; +import com.orientechnologies.orient.core.storage.cache.OReadCache; +import com.orientechnologies.orient.core.storage.cache.OWriteCache; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Persistent Set implementation that uses the SBTree to handle entries in persistent way. + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OIndexRIDContainer implements Set { + public static final String INDEX_FILE_EXTENSION = ".irs"; + + private final long fileId; + private Set underlying; + private boolean isEmbedded; + private int topThreshold = OGlobalConfiguration.INDEX_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.getValueAsInteger(); + private int bottomThreshold = OGlobalConfiguration.INDEX_SBTREEBONSAI_TO_EMBEDDED_THRESHOLD.getValueAsInteger(); + private final boolean durableNonTxMode; + + /** + * Should be called inside of lock to ensure uniqueness of entity on disk !!! + */ + public OIndexRIDContainer(String name, boolean durableNonTxMode) { + fileId = resolveFileIdByName(name + INDEX_FILE_EXTENSION); + underlying = new HashSet(); + isEmbedded = true; + this.durableNonTxMode = durableNonTxMode; + } + + public void setTopThreshold(int topThreshold) { + this.topThreshold = topThreshold; + } + + public void setBottomThreshold(int bottomThreshold) { + this.bottomThreshold = bottomThreshold; + } + + private long resolveFileIdByName(String fileName) { + final OAbstractPaginatedStorage storage = (OAbstractPaginatedStorage) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() + .getUnderlying(); + final OAtomicOperation atomicOperation; + try { + atomicOperation = storage.getAtomicOperationsManager().startAtomicOperation(fileName, true); + } catch (IOException e) { + throw OException.wrapException(new OIndexEngineException("Error creation of sbtree with name " + fileName, fileName), e); + } + + try { + final OReadCache readCache = storage.getReadCache(); + final OWriteCache writeCache = storage.getWriteCache(); + + if (atomicOperation == null) { + if (writeCache.exists(fileName)) + return writeCache.fileIdByName(fileName); + + return readCache.addFile(fileName, writeCache); + } else { + long fileId; + + if (atomicOperation.isFileExists(fileName)) + fileId = atomicOperation.loadFile(fileName); + else + fileId = atomicOperation.addFile(fileName); + + storage.getAtomicOperationsManager().endAtomicOperation(false, null, fileName); + return fileId; + } + } catch (IOException e) { + try { + storage.getAtomicOperationsManager().endAtomicOperation(true, e, fileName); + } catch (IOException ioe) { + throw OException.wrapException(new OIndexEngineException("Error of rollback of atomic operation", fileName), ioe); + } + + throw OException.wrapException(new OIndexEngineException("Error creation of sbtree with name " + fileName, fileName), e); + } + } + + public OIndexRIDContainer(long fileId, Set underlying, boolean durableNonTxMode) { + this.fileId = fileId; + this.underlying = underlying; + isEmbedded = !(underlying instanceof OIndexRIDContainerSBTree); + this.durableNonTxMode = durableNonTxMode; + } + + public long getFileId() { + return fileId; + } + + @Override + public int size() { + return underlying.size(); + } + + @Override + public boolean isEmpty() { + return underlying.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return underlying.contains(o); + } + + @Override + public Iterator iterator() { + return underlying.iterator(); + } + + @Override + public Object[] toArray() { + return underlying.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return underlying.toArray(a); + } + + @Override + public boolean add(OIdentifiable oIdentifiable) { + final boolean res = underlying.add(oIdentifiable); + checkTopThreshold(); + return res; + } + + @Override + public boolean remove(Object o) { + final boolean res = underlying.remove(o); + checkBottomThreshold(); + return res; + } + + @Override + public boolean containsAll(Collection c) { + return underlying.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + final boolean res = underlying.addAll(c); + checkTopThreshold(); + return res; + } + + @Override + public boolean retainAll(Collection c) { + return underlying.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + final boolean res = underlying.removeAll(c); + checkBottomThreshold(); + return res; + } + + @Override + public void clear() { + if (isEmbedded) + underlying.clear(); + else { + final OIndexRIDContainerSBTree tree = (OIndexRIDContainerSBTree) underlying; + tree.delete(); + underlying = new HashSet(); + isEmbedded = true; + } + } + + public boolean isEmbedded() { + return isEmbedded; + } + + public boolean isDurableNonTxMode() { + return durableNonTxMode; + } + + public Set getUnderlying() { + return underlying; + } + + private void checkTopThreshold() { + if (isEmbedded && topThreshold < underlying.size()) + convertToSbTree(); + } + + private void checkBottomThreshold() { + if (!isEmbedded && bottomThreshold > underlying.size()) + convertToEmbedded(); + } + + private void convertToEmbedded() { + final OIndexRIDContainerSBTree tree = (OIndexRIDContainerSBTree) underlying; + + final Set set = new HashSet(tree); + + tree.delete(); + underlying = set; + isEmbedded = true; + } + + /** + * If set is embedded convert it not embedded representation. + */ + public void checkNotEmbedded() { + if (isEmbedded) + convertToSbTree(); + } + + private void convertToSbTree() { + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OIndexRIDContainerSBTree tree = new OIndexRIDContainerSBTree(fileId, + (OAbstractPaginatedStorage) db.getStorage().getUnderlying()); + + tree.addAll(underlying); + + underlying = tree; + isEmbedded = false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainerSBTree.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainerSBTree.java new file mode 100755 index 00000000000..7c7e65e12de --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OIndexRIDContainerSBTree.java @@ -0,0 +1,248 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.common.serialization.types.OBooleanSerializer; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.sbtree.OSBTreeMapEntryIterator; +import com.orientechnologies.orient.core.index.sbtree.OTreeInternal; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsaiLocal; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * Persistent Set implementation that uses the SBTree to handle entries in persistent way. + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OIndexRIDContainerSBTree implements Set { + public static final String INDEX_FILE_EXTENSION = ".irs"; + + /** + * Generates a lock name for the given index name. + * + * @param indexName the index name to generate the lock name for. + * + * @return the generated lock name. + */ + public static String generateLockName(String indexName) { + return indexName + INDEX_FILE_EXTENSION; + } + + private OSBTreeBonsaiLocal tree; + + protected static final OProfiler PROFILER = Orient.instance().getProfiler(); + + public OIndexRIDContainerSBTree(long fileId, OAbstractPaginatedStorage storage) { + String fileName; + + OAtomicOperation atomicOperation = storage.getAtomicOperationsManager().getCurrentOperation(); + if (atomicOperation == null) + fileName = storage.getWriteCache().fileNameById(fileId); + else + fileName = atomicOperation.fileNameById(fileId); + + tree = new OSBTreeBonsaiLocal(fileName.substring(0, fileName.length() - INDEX_FILE_EXTENSION.length()), + INDEX_FILE_EXTENSION, storage); + + tree.create(OLinkSerializer.INSTANCE, OBooleanSerializer.INSTANCE); + } + + public OIndexRIDContainerSBTree(long fileId, OBonsaiBucketPointer rootPointer, boolean durableMode, + OAbstractPaginatedStorage storage) { + String fileName; + + OAtomicOperation atomicOperation = storage.getAtomicOperationsManager().getCurrentOperation(); + if (atomicOperation == null) + fileName = storage.getWriteCache().fileNameById(fileId); + else + fileName = atomicOperation.fileNameById(fileId); + + tree = new OSBTreeBonsaiLocal(fileName.substring(0, fileName.length() - INDEX_FILE_EXTENSION.length()), + INDEX_FILE_EXTENSION, storage); + tree.load(rootPointer); + } + + public OIndexRIDContainerSBTree(String file, OBonsaiBucketPointer rootPointer, boolean durableMode, + OAbstractPaginatedStorage storage) { + tree = new OSBTreeBonsaiLocal(file, INDEX_FILE_EXTENSION, storage); + tree.load(rootPointer); + } + + public OBonsaiBucketPointer getRootPointer() { + return tree.getRootBucketPointer(); + } + + @Override + public int size() { + return (int) tree.size(); + } + + @Override + public boolean isEmpty() { + return tree.size() == 0L; + } + + @Override + public boolean contains(Object o) { + return o instanceof OIdentifiable && contains((OIdentifiable) o); + } + + public boolean contains(OIdentifiable o) { + return tree.get(o) != null; + } + + @Override + public Iterator iterator() { + return new TreeKeyIterator(tree, false); + } + + @Override + public Object[] toArray() { + // TODO replace with more efficient implementation + + final ArrayList list = new ArrayList(size()); + + for (OIdentifiable identifiable : this) { + list.add(identifiable); + } + + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + // TODO replace with more efficient implementation. + + final ArrayList list = new ArrayList(size()); + + for (OIdentifiable identifiable : this) { + list.add(identifiable); + } + + return list.toArray(a); + } + + @Override + public boolean add(OIdentifiable oIdentifiable) { + return this.tree.put(oIdentifiable, Boolean.TRUE); + } + + @Override + public boolean remove(Object o) { + return o instanceof OIdentifiable && remove((OIdentifiable) o); + } + + public boolean remove(OIdentifiable o) { + return tree.remove(o) != null; + } + + @Override + public boolean containsAll(Collection c) { + for (Object e : c) + if (!contains(e)) + return false; + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean modified = false; + for (OIdentifiable e : c) + if (add(e)) + modified = true; + return modified; + } + + @Override + public boolean retainAll(Collection c) { + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + for (Object o : c) { + modified |= remove(o); + } + + return modified; + } + + @Override + public void clear() { + tree.clear(); + } + + public void delete() { + tree.delete(); + } + + public String getName() { + return tree.getName(); + } + + private static class TreeKeyIterator implements Iterator { + private final boolean autoConvertToRecord; + private OSBTreeMapEntryIterator entryIterator; + + public TreeKeyIterator(OTreeInternal tree, boolean autoConvertToRecord) { + entryIterator = new OSBTreeMapEntryIterator(tree); + this.autoConvertToRecord = autoConvertToRecord; + } + + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public OIdentifiable next() { + final OIdentifiable identifiable = entryIterator.next().getKey(); + if (autoConvertToRecord) + return identifiable.getRecord(); + else + return identifiable; + } + + @Override + public void remove() { + entryIterator.remove(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/ORidBagDeleter.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/ORidBagDeleter.java new file mode 100755 index 00000000000..7d6bfeab871 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/ORidBagDeleter.java @@ -0,0 +1,42 @@ +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.orient.core.db.document.ODocumentFieldVisitor; +import com.orientechnologies.orient.core.db.document.ODocumentFieldWalker; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Created by tglman on 01/07/16. + */ +public final class ORidBagDeleter implements ODocumentFieldVisitor { + + public static void deleteAllRidBags(ODocument document) { + final ODocumentFieldWalker documentFieldWalker = new ODocumentFieldWalker(); + final ORidBagDeleter ridBagDeleter = new ORidBagDeleter(); + documentFieldWalker.walkDocument(document, ridBagDeleter); + } + + @Override + public Object visitField(OType type, OType linkedType, Object value) { + if (value instanceof ORidBag) + ((ORidBag) value).delete(); + + return value; + } + + @Override + public boolean goFurther(OType type, OType linkedType, Object value, Object newValue) { + return true; + } + + @Override + public boolean goDeeper(OType type, OType linkedType, Object value) { + return true; + } + + @Override + public boolean updateMode() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManager.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManager.java new file mode 100755 index 00000000000..93bb35627b0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManager.java @@ -0,0 +1,50 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; + +import java.util.Map; +import java.util.UUID; + +public interface OSBTreeCollectionManager { + public OSBTreeBonsai createAndLoadTree(int clusterId); + + OBonsaiCollectionPointer createSBTree(int clusterId, UUID ownerUUID); + + public OSBTreeBonsai loadSBTree(OBonsaiCollectionPointer collectionPointer); + + public void releaseSBTree(OBonsaiCollectionPointer collectionPointer); + + public void delete(OBonsaiCollectionPointer collectionPointer); + + UUID listenForChanges(ORidBag collection); + + void updateCollectionPointer(UUID uuid, OBonsaiCollectionPointer pointer); + + void clearPendingCollections(); + + Map changedIds(); + + void clearChangedIds(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerAbstract.java new file mode 100755 index 00000000000..e1c3a79527d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerAbstract.java @@ -0,0 +1,297 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.orientechnologies.common.concur.resource.OCloseable; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Iterator; +import java.util.UUID; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public abstract class OSBTreeCollectionManagerAbstract + implements OCloseable, OSBTreeCollectionManager, OOrientStartupListener, OOrientShutdownListener { + public static final String FILE_NAME_PREFIX = "collections_"; + public static final String DEFAULT_EXTENSION = ".sbc"; + + /** + * Generates a lock name for the given cluster ID. + * + * @param clusterId the cluster ID to generate the lock name for. + * + * @return the generated lock name. + */ + public static String generateLockName(int clusterId) { + return FILE_NAME_PREFIX + clusterId + DEFAULT_EXTENSION; + } + + private static final ConcurrentLinkedHashMap GLOBAL_TREE_CACHE = new ConcurrentLinkedHashMap.Builder() + .maximumWeightedCapacity(Long.MAX_VALUE).build(); + + private static final int GLOBAL_EVICTION_THRESHOLD = OGlobalConfiguration.SBTREEBONSAI_LINKBAG_CACHE_EVICTION_SIZE + .getValueAsInteger(); + private static final int GLOBAL_CACHE_MAX_SIZE = OGlobalConfiguration.SBTREEBONSAI_LINKBAG_CACHE_SIZE.getValueAsInteger(); + + private static final Object[] GLOBAL_LOCKS; + private static final int GLOBAL_SHIFT; + private static final int GLOBAL_MASK; + + static { + final int concurrencyLevel = Runtime.getRuntime().availableProcessors() * 8; + int size = 1; + + int shifted = 0; + while (size < concurrencyLevel) { + size <<= 1; + shifted++; + } + + GLOBAL_SHIFT = 32 - shifted; + GLOBAL_MASK = size - 1; + + final Object[] locks = new Object[size]; + for (int i = 0; i < locks.length; i++) + locks[i] = new Object(); + + GLOBAL_LOCKS = locks; + } + + protected final int evictionThreshold; + protected final int cacheMaxSize; + protected final int shift; + protected final int mask; + protected final Object[] locks; + private final ConcurrentLinkedHashMap treeCache; + private final OStorage storage; + + public OSBTreeCollectionManagerAbstract(OStorage storage) { + this(GLOBAL_TREE_CACHE, storage, GLOBAL_EVICTION_THRESHOLD, GLOBAL_CACHE_MAX_SIZE, GLOBAL_LOCKS); + } + + // for testing purposes + /* internal */ OSBTreeCollectionManagerAbstract(OStorage storage, int evictionThreshold, int cacheMaxSize) { + this(new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(Long.MAX_VALUE).build(), + storage, evictionThreshold, cacheMaxSize, null); + } + + private OSBTreeCollectionManagerAbstract(ConcurrentLinkedHashMap treeCache, OStorage storage, + int evictionThreshold, int cacheMaxSize, Object[] locks) { + this.treeCache = treeCache; + this.storage = storage; + + this.evictionThreshold = evictionThreshold; + this.cacheMaxSize = cacheMaxSize; + + if (locks == null) { + final int concurrencyLevel = Runtime.getRuntime().availableProcessors() * 8; + int size = 1; + + int shifted = 0; + while (size < concurrencyLevel) { + size <<= 1; + shifted++; + } + + shift = 32 - shifted; + mask = size - 1; + + locks = new Object[size]; + for (int i = 0; i < locks.length; i++) + locks[i] = new Object(); + } else { + shift = GLOBAL_SHIFT; + mask = GLOBAL_MASK; + } + + this.locks = locks; + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + @Override + public void onStartup() { + // do nothing + } + + @Override + public void onShutdown() { + treeCache.clear(); + } + + @Override + public OSBTreeBonsai createAndLoadTree(int clusterId) { + return loadSBTree(createSBTree(clusterId, null)); + } + + @Override + public OBonsaiCollectionPointer createSBTree(int clusterId, UUID ownerUUID) { + OSBTreeBonsai tree = createTree(clusterId); + return tree.getCollectionPointer(); + } + + @Override + public OSBTreeBonsai loadSBTree(OBonsaiCollectionPointer collectionPointer) { + final CacheKey cacheKey = new CacheKey(storage, collectionPointer); + final Object lock = treesSubsetLock(cacheKey); + + final OSBTreeBonsai tree; + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (lock) { + SBTreeBonsaiContainer container = treeCache.get(cacheKey); + if (container != null) { + container.usagesCounter++; + tree = container.tree; + } else { + tree = loadTree(collectionPointer); + if (tree != null) { + assert tree.getRootBucketPointer().equals(collectionPointer.getRootPointer()); + + container = new SBTreeBonsaiContainer(tree); + container.usagesCounter++; + + treeCache.put(cacheKey, container); + } + } + + } + + if (tree != null) + evict(); + + return tree; + } + + @Override + public void releaseSBTree(OBonsaiCollectionPointer collectionPointer) { + final CacheKey cacheKey = new CacheKey(storage, collectionPointer); + final Object lock = treesSubsetLock(cacheKey); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (lock) { + SBTreeBonsaiContainer container = treeCache.getQuietly(cacheKey); + assert container != null; + container.usagesCounter--; + assert container.usagesCounter >= 0; + } + + evict(); + } + + @Override + public void delete(OBonsaiCollectionPointer collectionPointer) { + final CacheKey cacheKey = new CacheKey(storage, collectionPointer); + final Object lock = treesSubsetLock(cacheKey); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (lock) { + SBTreeBonsaiContainer container = treeCache.getQuietly(cacheKey); + assert container != null; + + if (container.usagesCounter != 0) + throw new IllegalStateException("Cannot delete SBTreeBonsai instance because it is used in other thread."); + + treeCache.remove(cacheKey); + } + } + + private void evict() { + if (treeCache.size() <= cacheMaxSize) + return; + + for (CacheKey cacheKey : treeCache.ascendingKeySetWithLimit(evictionThreshold)) { + final Object treeLock = treesSubsetLock(cacheKey); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (treeLock) { + SBTreeBonsaiContainer container = treeCache.getQuietly(cacheKey); + if (container != null && container.usagesCounter == 0) + treeCache.remove(cacheKey); + } + } + } + + @Override + public void close() { + clear(); + } + + public void clear() { + for (Iterator i = treeCache.keySet().iterator(); i.hasNext(); ) { + final CacheKey cacheKey = i.next(); + if (cacheKey.storage == storage) + i.remove(); + } + } + + protected abstract OSBTreeBonsai createTree(int clusterId); + + protected abstract OSBTreeBonsai loadTree(OBonsaiCollectionPointer collectionPointer); + + int size() { + return treeCache.size(); + } + + private Object treesSubsetLock(CacheKey cacheKey) { + final int hashCode = cacheKey.hashCode(); + final int index = (hashCode >>> shift) & mask; + + return locks[index]; + } + + private static final class SBTreeBonsaiContainer { + private final OSBTreeBonsai tree; + private int usagesCounter = 0; + + private SBTreeBonsaiContainer(OSBTreeBonsai tree) { + this.tree = tree; + } + } + + private static final class CacheKey { + private final OStorage storage; + private final OBonsaiCollectionPointer pointer; + + public CacheKey(OStorage storage, OBonsaiCollectionPointer pointer) { + this.storage = storage; + this.pointer = pointer; + } + + @Override + public int hashCode() { + return storage.hashCode() ^ pointer.hashCode(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") // it's a private class used in a private context + @Override + public boolean equals(Object obj) { + final CacheKey other = (CacheKey) obj; + return this.storage == other.storage && this.pointer.equals(other.pointer); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerShared.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerShared.java new file mode 100755 index 00000000000..16ecc89a5e8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeCollectionManagerShared.java @@ -0,0 +1,155 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsaiLocal; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeCollectionManagerShared extends OSBTreeCollectionManagerAbstract + implements OOrientStartupListener, OOrientShutdownListener { + private final OAbstractPaginatedStorage storage; + private volatile ThreadLocal> collectionPointerChanges = new CollectionPointerChangesThreadLocal(); + + public OSBTreeCollectionManagerShared(OAbstractPaginatedStorage storage) { + super(storage); + + this.storage = storage; + } + + // for testing purposes + /* internal */ OSBTreeCollectionManagerShared(int evictionThreshold, int cacheMaxSize, OAbstractPaginatedStorage storage) { + super(storage, evictionThreshold, cacheMaxSize); + + this.storage = storage; + } + + @Override + public void onShutdown() { + collectionPointerChanges = null; + super.onShutdown(); + } + + @Override + public void onStartup() { + super.onStartup(); + if (collectionPointerChanges == null) + collectionPointerChanges = new CollectionPointerChangesThreadLocal(); + } + + @Override + public OBonsaiCollectionPointer createSBTree(int clusterId, UUID ownerUUID) { + final OBonsaiCollectionPointer pointer = super.createSBTree(clusterId, ownerUUID); + + if (ownerUUID != null) { + Map changedPointers = collectionPointerChanges.get(); + changedPointers.put(ownerUUID, pointer); + } + + return pointer; + } + + @Override + protected OSBTreeBonsaiLocal createTree(int clusterId) { + + OSBTreeBonsaiLocal tree = new OSBTreeBonsaiLocal(FILE_NAME_PREFIX + clusterId, + DEFAULT_EXTENSION, storage); + tree.create(OLinkSerializer.INSTANCE, OIntegerSerializer.INSTANCE); + + return tree; + } + + @Override + protected OSBTreeBonsai loadTree(OBonsaiCollectionPointer collectionPointer) { + String fileName; + OAtomicOperation atomicOperation = storage.getAtomicOperationsManager().getCurrentOperation(); + if (atomicOperation == null) { + fileName = storage.getWriteCache().fileNameById(collectionPointer.getFileId()); + } else { + fileName = atomicOperation.fileNameById(collectionPointer.getFileId()); + } + + OSBTreeBonsaiLocal tree = new OSBTreeBonsaiLocal( + fileName.substring(0, fileName.length() - DEFAULT_EXTENSION.length()), DEFAULT_EXTENSION, storage); + + if (tree.load(collectionPointer.getRootPointer())) + return tree; + else + return null; + } + + /** + * Change UUID to null to prevent its serialization to disk. + * + * @param collection + * + * @return + */ + @Override + public UUID listenForChanges(ORidBag collection) { + UUID ownerUUID = collection.getTemporaryId(); + if (ownerUUID != null) { + final OBonsaiCollectionPointer pointer = collection.getPointer(); + + Map changedPointers = collectionPointerChanges.get(); + changedPointers.put(ownerUUID, pointer); + } + + return null; + } + + @Override + public void updateCollectionPointer(UUID uuid, OBonsaiCollectionPointer pointer) { + } + + @Override + public void clearPendingCollections() { + } + + public Map changedIds() { + return collectionPointerChanges.get(); + } + + public void clearChangedIds() { + collectionPointerChanges.get().clear(); + } + + private static class CollectionPointerChangesThreadLocal extends ThreadLocal> { + @Override + protected Map initialValue() { + return new HashMap(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeRidBag.java b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeRidBag.java new file mode 100755 index 00000000000..d732c6c4a42 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/record/ridbag/sbtree/OSBTreeRidBag.java @@ -0,0 +1,1127 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.record.ridbag.sbtree; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.common.types.OModifiableInteger; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBagDelegate; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.sbtree.OTreeInternal; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsaiLocal; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.OStorageProxy; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORidBagDeleteSerializationOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORidBagUpdateSerializationOperation; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Persistent Set implementation that uses the SBTree to handle entries in persistent way. + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeRidBag implements ORidBagDelegate { + private final OSBTreeCollectionManager collectionManager = ODatabaseRecordThreadLocal.INSTANCE.get() + .getSbTreeCollectionManager(); + private final NavigableMap changes = new ConcurrentSkipListMap(); + /** + * Entries with not valid id. + */ + private final IdentityHashMap newEntries = new IdentityHashMap(); + private OBonsaiCollectionPointer collectionPointer; + private int size; + + private boolean autoConvertToRecord = true; + + private List> changeListeners; + private transient ORecord owner; + private boolean updateOwner = true; + + public static interface Change { + public static final int SIZE = OByteSerializer.BYTE_SIZE + OIntegerSerializer.INT_SIZE; + + void increment(); + + void decrement(); + + int applyTo(Integer value); + + /** + * Checks if put increment operation can be safely performed. + * + * @return true if increment operation can be safely performed. + */ + boolean isUndefined(); + + void applyDiff(int delta); + + int serialize(byte[] stream, int offset); + } + + private static class DiffChange implements Change { + private static final byte TYPE = 0; + private int delta; + + private DiffChange(int delta) { + this.delta = delta; + } + + @Override + public void increment() { + delta++; + } + + @Override + public void decrement() { + delta--; + } + + @Override + public int applyTo(Integer value) { + int result; + if (value == null) + result = delta; + else + result = value + delta; + + if (result < 0) + result = 0; + + return result; + } + + @Override + public boolean isUndefined() { + return delta < 0; + } + + @Override + public void applyDiff(int delta) { + this.delta += delta; + } + + @Override + public int serialize(byte[] stream, int offset) { + OByteSerializer.INSTANCE.serializeLiteral(TYPE, stream, offset); + OIntegerSerializer.INSTANCE.serializeLiteral(delta, stream, offset + OByteSerializer.BYTE_SIZE); + return OByteSerializer.BYTE_SIZE + OIntegerSerializer.INT_SIZE; + } + } + + private static class AbsoluteChange implements Change { + private static final byte TYPE = 1; + private int value; + + private AbsoluteChange(int value) { + this.value = value; + + checkPositive(); + } + + @Override + public void increment() { + value++; + } + + @Override + public void decrement() { + value--; + + checkPositive(); + } + + @Override + public int applyTo(Integer value) { + return this.value; + } + + @Override + public boolean isUndefined() { + return true; + } + + @Override + public void applyDiff(int delta) { + value += delta; + + checkPositive(); + } + + @Override + public int serialize(byte[] stream, int offset) { + OByteSerializer.INSTANCE.serializeLiteral(TYPE, stream, offset); + OIntegerSerializer.INSTANCE.serializeLiteral(value, stream, offset + OByteSerializer.BYTE_SIZE); + return OByteSerializer.BYTE_SIZE + OIntegerSerializer.INT_SIZE; + } + + private void checkPositive() { + if (value < 0) + value = 0; + } + } + + public static class ChangeSerializationHelper { + public static final ChangeSerializationHelper INSTANCE = new ChangeSerializationHelper(); + + public Change deserializeChange(final byte[] stream, final int offset) { + int value = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset + OByteSerializer.BYTE_SIZE); + switch (OByteSerializer.INSTANCE.deserializeLiteral(stream, offset)) { + case AbsoluteChange.TYPE: + return new AbsoluteChange(value); + case DiffChange.TYPE: + return new DiffChange(value); + default: + throw new IllegalArgumentException("Change type is incorrect"); + } + } + + public Map deserializeChanges(final byte[] stream, int offset) { + final int count = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + final HashMap res = new HashMap(); + for (int i = 0; i < count; i++) { + ORecordId rid = OLinkSerializer.INSTANCE.deserialize(stream, offset); + offset += OLinkSerializer.RID_SIZE; + Change change = ChangeSerializationHelper.INSTANCE.deserializeChange(stream, offset); + offset += Change.SIZE; + + final OIdentifiable identifiable; + if (rid.isTemporary() && rid.getRecord() != null) + identifiable = rid.getRecord(); + else + identifiable = rid; + + res.put(identifiable, change); + } + + return res; + } + + public void serializeChanges(Map changes, OBinarySerializer keySerializer, byte[] stream, int offset) { + OIntegerSerializer.INSTANCE.serializeLiteral(changes.size(), stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + for (Map.Entry entry : changes.entrySet()) { + K key = entry.getKey(); + if (((OIdentifiable) key).getIdentity().isTemporary()) + key = ((OIdentifiable) key).getRecord(); + + keySerializer.serialize(key, stream, offset); + offset += keySerializer.getObjectSize(key); + + offset += entry.getValue().serialize(stream, offset); + } + } + + public int getChangesSerializedSize(int changesCount) { + return changesCount * (OLinkSerializer.RID_SIZE + Change.SIZE); + } + } + + private final class RIDBagIterator implements Iterator, OResettable, OSizeable, OAutoConvertToRecord { + private final NavigableMap changedValues; + private final SBTreeMapEntryIterator sbTreeIterator; + private boolean convertToRecord; + private Iterator> newEntryIterator; + private Iterator> changedValuesIterator; + private Map.Entry nextChange; + private Map.Entry nextSBTreeEntry; + private OIdentifiable currentValue; + private int currentFinalCounter; + private int currentCounter; + private boolean currentRemoved; + + private RIDBagIterator(IdentityHashMap newEntries, + NavigableMap changedValues, SBTreeMapEntryIterator sbTreeIterator, boolean convertToRecord) { + newEntryIterator = newEntries.entrySet().iterator(); + this.changedValues = changedValues; + this.convertToRecord = convertToRecord; + this.changedValuesIterator = changedValues.entrySet().iterator(); + this.sbTreeIterator = sbTreeIterator; + + nextChange = nextChangedNotRemovedEntry(changedValuesIterator); + + if (sbTreeIterator != null) + nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator); + } + + @Override + public boolean hasNext() { + return newEntryIterator.hasNext() || nextChange != null || nextSBTreeEntry != null || (currentValue != null + && currentCounter < currentFinalCounter); + } + + @Override + public OIdentifiable next() { + currentRemoved = false; + if (currentCounter < currentFinalCounter) { + currentCounter++; + return currentValue; + } + + if (newEntryIterator.hasNext()) { + Map.Entry entry = newEntryIterator.next(); + currentValue = entry.getKey(); + currentFinalCounter = entry.getValue().intValue(); + currentCounter = 1; + return currentValue; + } + + if (nextChange != null && nextSBTreeEntry != null) { + if (nextChange.getKey().compareTo(nextSBTreeEntry.getKey()) < 0) { + currentValue = nextChange.getKey(); + currentFinalCounter = nextChange.getValue().applyTo(0); + currentCounter = 1; + + nextChange = nextChangedNotRemovedEntry(changedValuesIterator); + } else { + currentValue = nextSBTreeEntry.getKey(); + currentFinalCounter = nextSBTreeEntry.getValue(); + currentCounter = 1; + + nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator); + if (nextChange != null && nextChange.getKey().equals(currentValue)) + nextChange = nextChangedNotRemovedEntry(changedValuesIterator); + } + } else if (nextChange != null) { + currentValue = nextChange.getKey(); + currentFinalCounter = nextChange.getValue().applyTo(0); + currentCounter = 1; + + nextChange = nextChangedNotRemovedEntry(changedValuesIterator); + } else if (nextSBTreeEntry != null) { + currentValue = nextSBTreeEntry.getKey(); + currentFinalCounter = nextSBTreeEntry.getValue(); + currentCounter = 1; + + nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator); + } else + throw new NoSuchElementException(); + + if (convertToRecord) + return currentValue.getRecord(); + + return currentValue; + } + + @Override + public void remove() { + if (currentRemoved) + throw new IllegalStateException("Current element has already been removed"); + + if (currentValue == null) + throw new IllegalStateException("Next method was not called for given iterator"); + + if (removeFromNewEntries(currentValue)) { + if (size >= 0) + size--; + } else { + Change counter = changedValues.get(currentValue); + if (counter != null) { + counter.decrement(); + if (size >= 0) + if (counter.isUndefined()) + size = -1; + else + size--; + } else { + if (nextChange != null) { + changedValues.put(currentValue, new DiffChange(-1)); + changedValuesIterator = changedValues.tailMap(nextChange.getKey(), false).entrySet().iterator(); + } else { + changedValues.put(currentValue, new DiffChange(-1)); + } + + size = -1; + } + } + + if (OSBTreeRidBag.this.owner != null) + ORecordInternal.unTrack(OSBTreeRidBag.this.owner, currentValue); + + if (updateOwner) + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, currentValue, null, + currentValue, false)); + currentRemoved = true; + } + + @Override + public void reset() { + newEntryIterator = newEntries.entrySet().iterator(); + + this.changedValuesIterator = changedValues.entrySet().iterator(); + if (sbTreeIterator != null) + this.sbTreeIterator.reset(); + + nextChange = nextChangedNotRemovedEntry(changedValuesIterator); + + if (sbTreeIterator != null) + nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator); + } + + @Override + public int size() { + return OSBTreeRidBag.this.size(); + } + + @Override + public boolean isAutoConvertToRecord() { + return convertToRecord; + } + + @Override + public void setAutoConvertToRecord(final boolean convertToRecord) { + this.convertToRecord = convertToRecord; + } + + private Map.Entry nextChangedNotRemovedEntry(Iterator> iterator) { + Map.Entry entry; + + while (iterator.hasNext()) { + entry = iterator.next(); + // TODO workaround + if (entry.getValue().applyTo(0) > 0) + return entry; + } + + return null; + } + } + + private final class SBTreeMapEntryIterator implements Iterator>, OResettable { + private final int prefetchSize; + private LinkedList> preFetchedValues; + private OIdentifiable firstKey; + + public SBTreeMapEntryIterator(int prefetchSize) { + this.prefetchSize = prefetchSize; + + init(); + } + + @Override + public boolean hasNext() { + return preFetchedValues != null; + } + + @Override + public Map.Entry next() { + final Map.Entry entry = preFetchedValues.removeFirst(); + if (preFetchedValues.isEmpty()) + prefetchData(false); + + return entry; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void reset() { + init(); + } + + private void prefetchData(boolean firstTime) { + final OSBTreeBonsai tree = loadTree(); + try { + tree.loadEntriesMajor(firstKey, firstTime, true, new OTreeInternal.RangeResultListener() { + @Override + public boolean addResult(final Map.Entry entry) { + preFetchedValues.add(new Map.Entry() { + @Override + public OIdentifiable getKey() { + return entry.getKey(); + } + + @Override + public Integer getValue() { + return entry.getValue(); + } + + @Override + public Integer setValue(Integer v) { + throw new UnsupportedOperationException("setValue"); + } + }); + + return preFetchedValues.size() <= prefetchSize; + } + }); + } finally { + releaseTree(); + } + + if (preFetchedValues.isEmpty()) + preFetchedValues = null; + else + firstKey = preFetchedValues.getLast().getKey(); + } + + private void init() { + OSBTreeBonsai tree = loadTree(); + try { + firstKey = tree.firstKey(); + } finally { + releaseTree(); + } + + if (firstKey == null) { + this.preFetchedValues = null; + return; + } + + this.preFetchedValues = new LinkedList>(); + prefetchData(true); + } + } + + public OSBTreeRidBag() { + collectionPointer = null; + } + + @Override + public ORecord getOwner() { + return owner; + } + + @Override + public void setOwner(ORecord owner) { + if (owner != null && this.owner != null && !this.owner.equals(owner)) { + throw new IllegalStateException("This data structure is owned by document " + owner + + " if you want to use it in other document create new rid bag instance and copy content of current one."); + } + if (this.owner != null) { + for (OIdentifiable entry : newEntries.keySet()) { + ORecordInternal.unTrack(this.owner, entry); + } + for (OIdentifiable entry : changes.keySet()) { + ORecordInternal.unTrack(this.owner, entry); + } + } + + this.owner = owner; + if (this.owner != null) { + for (OIdentifiable entry : newEntries.keySet()) { + ORecordInternal.track(this.owner, entry); + } + for (OIdentifiable entry : changes.keySet()) { + ORecordInternal.track(this.owner, entry); + } + } + } + + public Iterator iterator() { + return new RIDBagIterator(new IdentityHashMap(newEntries), changes, + collectionPointer != null ? new SBTreeMapEntryIterator(1000) : null, autoConvertToRecord); + } + + @Override + public Iterator rawIterator() { + return new RIDBagIterator(new IdentityHashMap(newEntries), changes, + collectionPointer != null ? new SBTreeMapEntryIterator(1000) : null, false); + } + + @Override + public void convertLinks2Records() { + TreeMap newChanges = new TreeMap(); + for (Map.Entry entry : changes.entrySet()) { + final OIdentifiable key = entry.getKey().getRecord(); + if (key != null && this.owner != null) { + ORecordInternal.unTrack(this.owner, entry.getKey()); + ORecordInternal.track(this.owner, key); + } + newChanges.put((key == null) ? entry.getKey() : key, entry.getValue()); + } + + changes.clear(); + changes.putAll(newChanges); + } + + @Override + public boolean convertRecords2Links() { + final Map newChangedValues = new HashMap(); + for (Map.Entry entry : changes.entrySet()) { + OIdentifiable identifiable = entry.getKey(); + if (identifiable instanceof ORecord) { + ORID identity = identifiable.getIdentity(); + ORecord record = (ORecord) identifiable; + identity = record.getIdentity(); + + newChangedValues.put(identity, entry.getValue()); + } else + newChangedValues.put(entry.getKey().getIdentity(), entry.getValue()); + } + + for (Map.Entry entry : newChangedValues.entrySet()) { + if (entry.getKey() instanceof ORecord) { + ORecord record = (ORecord) entry.getKey(); + + newChangedValues.put(record, entry.getValue()); + } else + return false; + } + + newEntries.clear(); + + changes.clear(); + changes.putAll(newChangedValues); + + return true; + } + + public void mergeChanges(OSBTreeRidBag treeRidBag) { + for (Map.Entry entry : treeRidBag.newEntries.entrySet()) { + mergeDiffEntry(entry.getKey(), entry.getValue().getValue()); + } + + for (Map.Entry entry : treeRidBag.changes.entrySet()) { + final OIdentifiable rec = entry.getKey(); + final Change change = entry.getValue(); + final int diff; + if (change instanceof DiffChange) + diff = ((DiffChange) change).delta; + else if (change instanceof AbsoluteChange) + diff = ((AbsoluteChange) change).value - getAbsoluteValue(rec).value; + else + throw new IllegalArgumentException("change type is not supported"); + + mergeDiffEntry(rec, diff); + } + } + + @Override + public boolean isAutoConvertToRecord() { + return autoConvertToRecord; + } + + @Override + public void setAutoConvertToRecord(boolean convertToRecord) { + autoConvertToRecord = convertToRecord; + } + + @Override + public boolean detach() { + return convertRecords2Links(); + } + + public void addAll(Collection values) { + for (OIdentifiable identifiable : values) { + add(identifiable); + } + } + + public void add(final OIdentifiable identifiable) { + if (identifiable == null) + throw new IllegalArgumentException("Impossible to add a null identifiable in a ridbag"); + + if (identifiable.getIdentity().isValid()) { + Change counter = changes.get(identifiable); + if (counter == null) + changes.put(identifiable, new DiffChange(1)); + else { + if (counter.isUndefined()) { + counter = getAbsoluteValue(identifiable); + changes.put(identifiable, counter); + } + counter.increment(); + } + } else { + final OModifiableInteger counter = newEntries.get(identifiable); + if (counter == null) + newEntries.put(identifiable, new OModifiableInteger(1)); + else + counter.increment(); + } + + if (size >= 0) + size++; + + if (this.owner != null) + ORecordInternal.track(this.owner, identifiable); + + if (updateOwner) + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.ADD, identifiable, + identifiable, null, false)); + } + + public void remove(OIdentifiable identifiable) { + if (removeFromNewEntries(identifiable)) { + if (size >= 0) + size--; + } else { + final Change counter = changes.get(identifiable); + if (counter == null) { + // Not persistent keys can only be in changes or newEntries + if (identifiable.getIdentity().isPersistent()) { + changes.put(identifiable, new DiffChange(-1)); + size = -1; + } else + // Return immediately to prevent firing of event + return; + } else { + counter.decrement(); + + if (size >= 0) + if (counter.isUndefined()) + size = -1; + else + size--; + } + } + + if (this.owner != null) + ORecordInternal.unTrack(this.owner, identifiable); + + if (updateOwner) + fireCollectionChangedEvent( + new OMultiValueChangeEvent(OMultiValueChangeEvent.OChangeType.REMOVE, identifiable, null, + identifiable, false)); + } + + @Override + public boolean contains(OIdentifiable identifiable) { + if (newEntries.containsKey(identifiable)) + return true; + + Change counter = changes.get(identifiable); + + if (counter != null) { + AbsoluteChange absoluteValue = getAbsoluteValue(identifiable); + + if (counter.isUndefined()) { + changes.put(identifiable, absoluteValue); + } + + counter = absoluteValue; + } else { + counter = getAbsoluteValue(identifiable); + } + + return counter.applyTo(0) > 0; + } + + public int size() { + if (size >= 0) + return size; + else { + return updateSize(); + } + } + + @Override + public String toString() { + if (size >= 0) + return "[size=" + size + "]"; + + return "[...]"; + } + + public boolean isEmpty() { + return size() == 0; + } + + public void addChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners == null) + changeListeners = new LinkedList>(); + changeListeners.add(changeListener); + } + + public void removeRecordChangeListener(final OMultiValueChangeListener changeListener) { + if (changeListeners != null) + changeListeners.remove(changeListener); + } + + @Override + public Class getGenericClass() { + return OIdentifiable.class; + } + + @Override + public Object returnOriginalState(List> multiValueChangeEvents) { + final OSBTreeRidBag reverted = new OSBTreeRidBag(); + for (OIdentifiable identifiable : this) + reverted.add(identifiable); + + final ListIterator> listIterator = multiValueChangeEvents + .listIterator(multiValueChangeEvents.size()); + + while (listIterator.hasPrevious()) { + final OMultiValueChangeEvent event = listIterator.previous(); + switch (event.getChangeType()) { + case ADD: + reverted.remove(event.getKey()); + break; + case REMOVE: + reverted.add(event.getOldValue()); + break; + default: + throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); + } + } + + return reverted; + } + + @Override + public int getSerializedSize() { + int result = 2 * OLongSerializer.LONG_SIZE + 3 * OIntegerSerializer.INT_SIZE; + if (ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() instanceof OStorageProxy + || ORecordSerializationContext.getContext() == null) + result += getChangesSerializedSize(); + return result; + } + + @Override + public int getSerializedSize(byte[] stream, int offset) { + return getSerializedSize(); + } + + @Override + public int serialize(byte[] stream, int offset, UUID ownerUuid) { + for (Map.Entry entry : newEntries.entrySet()) { + OIdentifiable identifiable = entry.getKey(); + assert identifiable instanceof ORecord; + Change c = changes.get(identifiable); + + final int delta = entry.getValue().intValue(); + if (c == null) + changes.put(identifiable, new DiffChange(delta)); + else + c.applyDiff(delta); + } + newEntries.clear(); + + final ORecordSerializationContext context; + boolean remoteMode = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() instanceof OStorageProxy; + if (remoteMode) { + context = null; + } else + context = ORecordSerializationContext.getContext(); + + // make sure that we really save underlying record. + if (collectionPointer == null) { + if (context != null) { + final int clusterId = getHighLevelDocClusterId(); + assert clusterId > -1; + collectionPointer = ODatabaseRecordThreadLocal.INSTANCE.get().getSbTreeCollectionManager() + .createSBTree(clusterId, ownerUuid); + } + } + + OBonsaiCollectionPointer collectionPointer; + if (this.collectionPointer != null) + collectionPointer = this.collectionPointer; + else { + collectionPointer = OBonsaiCollectionPointer.INVALID; + } + + OLongSerializer.INSTANCE.serializeLiteral(collectionPointer.getFileId(), stream, offset); + offset += OLongSerializer.LONG_SIZE; + + OBonsaiBucketPointer rootPointer = collectionPointer.getRootPointer(); + OLongSerializer.INSTANCE.serializeLiteral(rootPointer.getPageIndex(), stream, offset); + offset += OLongSerializer.LONG_SIZE; + + OIntegerSerializer.INSTANCE.serializeLiteral(rootPointer.getPageOffset(), stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + // Keep this section for binary compatibility with versions older then 1.7.5 + OIntegerSerializer.INSTANCE.serializeLiteral(size, stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + if (context == null) { + ChangeSerializationHelper.INSTANCE.serializeChanges(changes, OLinkSerializer.INSTANCE, stream, offset); + } else { + + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + for (Entry change : this.changes.entrySet()) { + OIdentifiable key = change.getKey(); + if (db != null && db.getTransaction().isActive()) { + if (!key.getIdentity().isPersistent()) { + OIdentifiable newKey = db.getTransaction().getRecord(key.getIdentity()); + if (newKey != null) { + changes.remove(key); + changes.put(newKey, change.getValue()); + } + } + } + } + this.collectionPointer = collectionPointer; + context.push(new ORidBagUpdateSerializationOperation(changes, collectionPointer)); + + // 0-length serialized list of changes + OIntegerSerializer.INSTANCE.serializeLiteral(0, stream, offset); + offset += OIntegerSerializer.INT_SIZE; + } + + return offset; + } + + public void clearChanges() { + changes.clear(); + } + + @Override + public void requestDelete() { + final ORecordSerializationContext context = ORecordSerializationContext.getContext(); + if (context != null && collectionPointer != null) + context.push(new ORidBagDeleteSerializationOperation(collectionPointer, this)); + } + + public void confirmDelete() { + collectionPointer = null; + changes.clear(); + newEntries.clear(); + size = 0; + if (changeListeners != null) + changeListeners.clear(); + changeListeners = null; + } + + @Override + public int deserialize(byte[] stream, int offset) { + final long fileId = OLongSerializer.INSTANCE.deserializeLiteral(stream, offset); + offset += OLongSerializer.LONG_SIZE; + + final long pageIndex = OLongSerializer.INSTANCE.deserializeLiteral(stream, offset); + offset += OLongSerializer.LONG_SIZE; + + final int pageOffset = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset); + offset += OIntegerSerializer.INT_SIZE; + + // Cached bag size. Not used after 1.7.5 + offset += OIntegerSerializer.INT_SIZE; + + if (fileId == -1) + collectionPointer = null; + else + collectionPointer = new OBonsaiCollectionPointer(fileId, new OBonsaiBucketPointer(pageIndex, pageOffset)); + + this.size = -1; + + changes.putAll(ChangeSerializationHelper.INSTANCE.deserializeChanges(stream, offset)); + + return offset; + } + + public OBonsaiCollectionPointer getCollectionPointer() { + return collectionPointer; + } + + public void setCollectionPointer(OBonsaiCollectionPointer collectionPointer) { + this.collectionPointer = collectionPointer; + } + + @Override + public List> getChangeListeners() { + if (changeListeners == null) + return Collections.emptyList(); + return Collections.unmodifiableList(changeListeners); + } + + public void fireCollectionChangedEvent(final OMultiValueChangeEvent event) { + if (changeListeners != null) { + for (final OMultiValueChangeListener changeListener : changeListeners) { + if (changeListener != null) + changeListener.onAfterRecordChanged(event); + } + } + } + + private OSBTreeBonsai loadTree() { + if (collectionPointer == null) + return null; + + return collectionManager.loadSBTree(collectionPointer); + } + + private void releaseTree() { + if (collectionPointer == null) + return; + + collectionManager.releaseSBTree(collectionPointer); + } + + private void mergeDiffEntry(OIdentifiable key, int diff) { + if (diff > 0) { + for (int i = 0; i < diff; i++) { + add(key); + } + } else { + for (int i = diff; i < 0; i++) { + remove(key); + } + } + } + + private AbsoluteChange getAbsoluteValue(OIdentifiable identifiable) { + final OSBTreeBonsai tree = loadTree(); + try { + Integer oldValue; + + if (tree == null) + oldValue = 0; + else + oldValue = tree.get(identifiable); + + if (oldValue == null) + oldValue = 0; + + final Change change = changes.get(identifiable); + + return new AbsoluteChange(change == null ? oldValue : change.applyTo(oldValue)); + } finally { + releaseTree(); + } + } + + /** + * Recalculates real bag size. + * + * @return real size + */ + private int updateSize() { + int size = 0; + if (collectionPointer != null) { + final OSBTreeBonsai tree = loadTree(); + try { + size = tree.getRealBagSize(changes); + } finally { + releaseTree(); + } + } else { + for (Change change : changes.values()) { + size += change.applyTo(0); + } + } + + for (OModifiableInteger diff : newEntries.values()) { + size += diff.getValue(); + } + + this.size = size; + return size; + } + + private int getChangesSerializedSize() { + Set changedIds = new HashSet(changes.keySet()); + changedIds.addAll(newEntries.keySet()); + return ChangeSerializationHelper.INSTANCE.getChangesSerializedSize(changedIds.size()); + } + + private int getHighLevelDocClusterId() { + ORecordElement owner = this.owner; + while (owner != null && owner.getOwner() != null) { + owner = owner.getOwner(); + } + + if (owner != null) + return ((OIdentifiable) owner).getIdentity().getClusterId(); + + return -1; + } + + /** + * Removes entry with given key from {@link #newEntries}. + * + * @param identifiable key to remove + * + * @return true if entry have been removed + */ + private boolean removeFromNewEntries(final OIdentifiable identifiable) { + OModifiableInteger counter = newEntries.get(identifiable); + if (counter == null) + return false; + else { + if (counter.getValue() == 1) + newEntries.remove(identifiable); + else + counter.decrement(); + return true; + } + } + + private Map.Entry nextChangedNotRemovedSBTreeEntry(Iterator> iterator) { + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + final Change change = changes.get(entry.getKey()); + if (change == null) + return entry; + + final int newValue = change.applyTo(entry.getValue()); + + if (newValue > 0) + return new Map.Entry() { + @Override + public OIdentifiable getKey() { + return entry.getKey(); + } + + @Override + public Integer getValue() { + return newValue; + } + + @Override + public Integer setValue(Integer value) { + throw new UnsupportedOperationException(); + } + }; + } + + return null; + } + + public void debugPrint(PrintStream writer) throws IOException { + OSBTreeBonsai tree = loadTree(); + if (tree instanceof OSBTreeBonsaiLocal) { + ((OSBTreeBonsaiLocal) tree).debugPrintBucket(writer); + } + } + + @Override + public void replace(OMultiValueChangeEvent event, Object newValue) { + //do nothing not needed + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseCompare.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseCompare.java new file mode 100755 index 00000000000..c4f018f3e89 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseCompare.java @@ -0,0 +1,953 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import static com.orientechnologies.orient.core.record.impl.ODocumentHelper.makeDbCall; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexKeyCursor; +import com.orientechnologies.orient.core.index.OIndexManager; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper.ODbRelatedCall; +import com.orientechnologies.orient.core.storage.OPhysicalPosition; +import com.orientechnologies.orient.core.storage.ORawBuffer; +import com.orientechnologies.orient.core.storage.OStorage; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class ODatabaseCompare extends ODatabaseImpExpAbstract { + private ODatabaseDocumentTx databaseOne; + private ODatabaseDocumentTx databaseTwo; + + private boolean compareEntriesForAutomaticIndexes = false; + private boolean autoDetectExportImportMap = true; + + private OIndex exportImportHashTable = null; + private int differences = 0; + private boolean compareIndexMetadata = false; + + public ODatabaseCompare(String iDb1URL, String iDb2URL, final String userName, final String userPassword, + final OCommandOutputListener iListener) throws IOException { + super(null, null, iListener); + + listener.onMessage("\nComparing two local databases:\n1) " + iDb1URL + "\n2) " + iDb2URL + "\n"); + + databaseOne = new ODatabaseDocumentTx(iDb1URL); + databaseOne.open(userName, userPassword); + + databaseTwo = new ODatabaseDocumentTx(iDb2URL); + databaseTwo.open(userName, userPassword); + + // exclude automatically generated clusters + excludeClusters.add("orids"); + excludeClusters.add(OMetadataDefault.CLUSTER_INDEX_NAME); + excludeClusters.add(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME); + } + + @Override + public void run() { + compare(); + } + + public boolean compare() { + try { + ODocumentHelper.RIDMapper ridMapper = null; + if (autoDetectExportImportMap) { + listener.onMessage( + "\nAuto discovery of mapping between RIDs of exported and imported records is switched on, try to discover mapping data on disk."); + exportImportHashTable = (OIndex) databaseTwo.getMetadata().getIndexManager() + .getIndex(ODatabaseImport.EXPORT_IMPORT_MAP_NAME); + if (exportImportHashTable != null) { + listener.onMessage("\nMapping data were found and will be loaded."); + ridMapper = new ODocumentHelper.RIDMapper() { + @Override + public ORID map(ORID rid) { + if (rid == null) + return null; + + if (!rid.isPersistent()) + return null; + + databaseTwo.activateOnCurrentThread(); + final OIdentifiable result = exportImportHashTable.get(rid); + if (result == null) + return null; + + return result.getIdentity(); + } + }; + } else + listener.onMessage("\nMapping data were not found."); + } + + compareClusters(); + compareRecords(ridMapper); + + compareSchema(); + compareIndexes(ridMapper); + + if (differences == 0) { + listener.onMessage("\n\nDatabases match."); + return true; + } else { + listener.onMessage("\n\nDatabases do not match. Found " + differences + " difference(s)."); + return false; + } + } catch (Exception e) { + OLogManager.instance() + .error(this, "Error on comparing database '%s' against '%s'", e, databaseOne.getName(), databaseTwo.getName()); + throw new ODatabaseExportException( + "Error on comparing database '" + databaseOne.getName() + "' against '" + databaseTwo.getName() + "'", + e); + } finally { + makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Void call(ODatabaseDocumentInternal database) { + database.close(); + return null; + } + }); + makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public Void call(ODatabaseDocumentInternal database) { + database.close(); + return null; + } + }); + + } + } + + private void compareSchema() { + OSchema schema1 = databaseOne.getMetadata().getImmutableSchemaSnapshot(); + OSchema schema2 = databaseTwo.getMetadata().getImmutableSchemaSnapshot(); + boolean ok = true; + for (OClass clazz : schema1.getClasses()) { + OClass clazz2 = schema2.getClass(clazz.getName()); + + if (clazz2 == null) { + listener.onMessage("\n- ERR: Class definition " + clazz.getName() + " for DB2 is null."); + continue; + } + + final List sc1 = clazz.getSuperClassesNames(); + final List sc2 = clazz2.getSuperClassesNames(); + + if (!sc1.isEmpty() || !sc2.isEmpty()) { + if (!sc1.containsAll(sc2) || !sc2.containsAll(sc1)) { + listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in superclasses in DB2."); + ok = false; + } + } + if (!clazz.getClassIndexes().equals(clazz2.getClassIndexes())) { + listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in indexes in DB2."); + ok = false; + } + if (!Arrays.equals(clazz.getClusterIds(), clazz2.getClusterIds())) { + listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in clusters in DB2."); + ok = false; + } + if (!clazz.getCustomKeys().equals(clazz2.getCustomKeys())) { + listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in custom keys in DB2."); + ok = false; + } + if (clazz.getOverSize() != clazz2.getOverSize()) { + listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in overSize in DB2."); + ok = false; + } + + if (clazz.getDefaultClusterId() != clazz2.getDefaultClusterId()) { + listener + .onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in default cluser id in DB2."); + ok = false; + } + + // if (clazz.getSize() != clazz2.getSize()) { + // listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " in DB1 is not equals in size in DB2."); + // ok = false; + // } + + for (OProperty prop : clazz.declaredProperties()) { + OProperty prop2 = clazz2.getProperty(prop.getName()); + if (prop2 == null) { + listener + .onMessage("\n- ERR: Class definition for " + clazz.getName() + " as missed property " + prop.getName() + "in DB2."); + ok = false; + continue; + } + if (prop.getType() != prop2.getType()) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same type for property " + prop.getName() + "in DB2. "); + ok = false; + } + + if (prop.getLinkedType() != prop2.getLinkedType()) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same linkedtype for property " + prop.getName() + + "in DB2."); + ok = false; + } + + if (prop.getMin() != null) { + if (!prop.getMin().equals(prop2.getMin())) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same min for property " + prop.getName() + "in DB2."); + ok = false; + } + } + if (prop.getMax() != null) { + if (!prop.getMax().equals(prop2.getMax())) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same max for property " + prop.getName() + "in DB2."); + ok = false; + } + } + + if (prop.getMax() != null) { + if (!prop.getMax().equals(prop2.getMax())) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same regexp for property " + prop.getName() + + "in DB2."); + ok = false; + } + } + + if (prop.getLinkedClass() != null) { + if (!prop.getLinkedClass().equals(prop2.getLinkedClass())) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same linked class for property " + prop.getName() + + "in DB2."); + ok = false; + } + } + + if (prop.getLinkedClass() != null) { + if (!prop.getCustomKeys().equals(prop2.getCustomKeys())) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same custom keys for property " + prop.getName() + + "in DB2."); + ok = false; + } + } + if (prop.isMandatory() != prop2.isMandatory()) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same mandatory flag for property " + prop.getName() + + "in DB2."); + ok = false; + } + if (prop.isNotNull() != prop2.isNotNull()) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same nut null flag for property " + prop.getName() + + "in DB2."); + ok = false; + } + if (prop.isReadonly() != prop2.isReadonly()) { + listener.onMessage( + "\n- ERR: Class definition for " + clazz.getName() + " as not same readonly flag setting for property " + prop + .getName() + "in DB2."); + ok = false; + } + + } + if (!ok) { + ++differences; + ok = true; + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void compareIndexes(ODocumentHelper.RIDMapper ridMapper) { + listener.onMessage("\nStarting index comparison:"); + + boolean ok = true; + + final OIndexManager indexManagerOne = makeDbCall(databaseOne, new ODbRelatedCall() { + public OIndexManager call(ODatabaseDocumentInternal database) { + return database.getMetadata().getIndexManager(); + } + }); + + final OIndexManager indexManagerTwo = makeDbCall(databaseTwo, new ODbRelatedCall() { + public OIndexManager call(ODatabaseDocumentInternal database) { + return database.getMetadata().getIndexManager(); + } + }); + + final Collection> indexesOne = makeDbCall(databaseOne, + new ODbRelatedCall>>() { + public Collection> call(ODatabaseDocumentInternal database) { + return indexManagerOne.getIndexes(); + } + }); + + int indexesSizeOne = makeDbCall(databaseTwo, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return indexesOne.size(); + } + }); + + int indexesSizeTwo = makeDbCall(databaseTwo, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return indexManagerTwo.getIndexes().size(); + } + }); + + if (exportImportHashTable != null) + indexesSizeTwo--; + + if (indexesSizeOne != indexesSizeTwo) { + ok = false; + listener.onMessage("\n- ERR: Amount of indexes are different."); + listener.onMessage("\n--- DB1: " + indexesSizeOne); + listener.onMessage("\n--- DB2: " + indexesSizeTwo); + listener.onMessage("\n"); + ++differences; + } + + final Iterator> iteratorOne = makeDbCall(databaseOne, + new ODbRelatedCall>>() { + public Iterator> call(ODatabaseDocumentInternal database) { + return indexesOne.iterator(); + } + }); + + while (makeDbCall(databaseOne, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return iteratorOne.hasNext(); + } + })) { + final OIndex indexOne = makeDbCall(databaseOne, new ODbRelatedCall>() { + public OIndex call(ODatabaseDocumentInternal database) { + return iteratorOne.next(); + } + }); + + final OIndex indexTwo = makeDbCall(databaseTwo, new ODbRelatedCall>() { + public OIndex call(ODatabaseDocumentInternal database) { + return indexManagerTwo.getIndex(indexOne.getName()); + } + }); + + if (indexTwo == null) { + ok = false; + listener.onMessage("\n- ERR: Index " + indexOne.getName() + " is absent in DB2."); + ++differences; + continue; + } + + if (!indexOne.getType().equals(indexTwo.getType())) { + ok = false; + listener.onMessage("\n- ERR: Index types for index " + indexOne.getName() + " are different."); + listener.onMessage("\n--- DB1: " + indexOne.getType()); + listener.onMessage("\n--- DB2: " + indexTwo.getType()); + listener.onMessage("\n"); + ++differences; + continue; + } + + if (!indexOne.getClusters().equals(indexTwo.getClusters())) { + ok = false; + listener.onMessage("\n- ERR: Clusters to index for index " + indexOne.getName() + " are different."); + listener.onMessage("\n--- DB1: " + indexOne.getClusters()); + listener.onMessage("\n--- DB2: " + indexTwo.getClusters()); + listener.onMessage("\n"); + ++differences; + continue; + } + + if (indexOne.getDefinition() == null && indexTwo.getDefinition() != null) { + ok = false; + listener.onMessage("\n- ERR: Index definition for index " + indexOne.getName() + " for DB2 is not null."); + ++differences; + continue; + } else if (indexOne.getDefinition() != null && indexTwo.getDefinition() == null) { + ok = false; + listener.onMessage("\n- ERR: Index definition for index " + indexOne.getName() + " for DB2 is null."); + ++differences; + continue; + } else if (indexOne.getDefinition() != null && !indexOne.getDefinition().equals(indexTwo.getDefinition())) { + ok = false; + listener.onMessage("\n- ERR: Index definitions for index " + indexOne.getName() + " are different."); + listener.onMessage("\n--- DB1: " + indexOne.getDefinition()); + listener.onMessage("\n--- DB2: " + indexTwo.getDefinition()); + listener.onMessage("\n"); + ++differences; + continue; + } + + final long indexOneSize = makeDbCall(databaseOne, new ODbRelatedCall() { + public Long call(ODatabaseDocumentInternal database) { + return indexOne.getSize(); + } + }); + + final long indexTwoSize = makeDbCall(databaseTwo, new ODbRelatedCall() { + public Long call(ODatabaseDocumentInternal database) { + return indexTwo.getSize(); + } + }); + + if (indexOneSize != indexTwoSize) { + ok = false; + listener.onMessage("\n- ERR: Amount of entries for index " + indexOne.getName() + " are different."); + listener.onMessage("\n--- DB1: " + indexOneSize); + listener.onMessage("\n--- DB2: " + indexTwoSize); + listener.onMessage("\n"); + ++differences; + } + + if (compareIndexMetadata) { + final ODocument metadataOne = indexOne.getMetadata(); + final ODocument metadataTwo = indexTwo.getMetadata(); + + if (metadataOne == null && metadataTwo != null) { + ok = false; + listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 is null but for DB2 is not."); + listener.onMessage("\n"); + ++differences; + } else if (metadataOne != null && metadataTwo == null) { + ok = false; + listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 is not null but for DB2 is null."); + listener.onMessage("\n"); + ++differences; + } else if (metadataOne != null && metadataTwo != null && !ODocumentHelper + .hasSameContentOf(metadataOne, databaseOne, metadataTwo, databaseTwo, ridMapper)) { + ok = false; + listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 and for DB2 are different."); + makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Object call(ODatabaseDocumentInternal database) { + listener.onMessage("\n--- M1: " + metadataOne); + return null; + } + }); + makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public Object call(ODatabaseDocumentInternal database) { + listener.onMessage("\n--- M2: " + metadataTwo); + return null; + } + }); + listener.onMessage("\n"); + ++differences; + } + } + + if (((compareEntriesForAutomaticIndexes && !indexOne.getType().equals("DICTIONARY")) || !indexOne.isAutomatic())) { + final OIndexKeyCursor indexKeyCursorOne = makeDbCall(databaseOne, new ODbRelatedCall() { + public OIndexKeyCursor call(ODatabaseDocumentInternal database) { + return indexOne.keyCursor(); + } + }); + + Object key = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Object call(ODatabaseDocumentInternal database) { + return indexKeyCursorOne.next(-1); + } + }); + + while (key != null) { + final Object indexKey = key; + + Object indexOneValue = makeDbCall(databaseOne, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return indexOne.get(indexKey); + } + }); + + final Object indexTwoValue = makeDbCall(databaseTwo, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return indexTwo.get(indexKey); + } + }); + + if (indexTwoValue == null) { + ok = false; + listener.onMessage("\n- ERR: Entry with key " + key + " is absent in index " + indexOne.getName() + " for DB2."); + ++differences; + } else if (indexOneValue instanceof Set && indexTwoValue instanceof Set) { + final Set indexOneValueSet = (Set) indexOneValue; + final Set indexTwoValueSet = (Set) indexTwoValue; + + if (!ODocumentHelper + .compareSets(databaseOne, indexOneValueSet, databaseTwo, indexTwoValueSet, ridMapper)) { + ok = false; + reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue); + } + } else if (indexOneValue instanceof ORID && indexTwoValue instanceof ORID) { + if (ridMapper != null && ((ORID) indexOneValue).isPersistent()) { + OIdentifiable identifiable = ridMapper.map((ORID) indexOneValue); + + if (identifiable != null) + indexOneValue = identifiable.getIdentity(); + } + if (!indexOneValue.equals(indexTwoValue)) { + ok = false; + reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue); + } + } else if (!indexOneValue.equals(indexTwoValue)) { + ok = false; + reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue); + } + + key = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Object call(ODatabaseDocumentInternal database) { + return indexKeyCursorOne.next(-1); + } + }); + } + } + } + + if (ok) + listener.onMessage("OK"); + } + + private boolean compareClusters() { + listener.onMessage("\nStarting shallow comparison of clusters:"); + + listener.onMessage("\nChecking the number of clusters..."); + + Collection clusterNames1 = makeDbCall(databaseOne, new ODbRelatedCall>() { + @Override + public Collection call(ODatabaseDocumentInternal database) { + return database.getClusterNames(); + } + }); + + Collection clusterNames2 = makeDbCall(databaseTwo, new ODbRelatedCall>() { + @Override + public Collection call(ODatabaseDocumentInternal database) { + return database.getClusterNames(); + } + }); + + if (clusterNames1.size() != clusterNames2.size()) { + listener.onMessage("ERR: cluster sizes are different: " + clusterNames1.size() + " <-> " + clusterNames2.size()); + ++differences; + } + + boolean ok; + + for (final String clusterName : clusterNames1) { + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(clusterName)) + continue; + } else if (excludeClusters != null) { + if (excludeClusters.contains(clusterName)) + continue; + } + + ok = true; + final int cluster1Id = makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public Integer call(ODatabaseDocumentInternal database) { + return database.getClusterIdByName(clusterName); + } + }); + + listener.onMessage("\n- Checking cluster " + String.format("%-25s: ", "'" + clusterName + "'")); + + if (cluster1Id == -1) { + listener.onMessage("ERR: cluster name '" + clusterName + "' was not found on database " + databaseTwo.getName()); + ++differences; + ok = false; + } + + final int cluster2Id = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Integer call(ODatabaseDocumentInternal database) { + return database.getClusterIdByName(clusterName); + } + }); + if (cluster1Id != cluster2Id) { + listener.onMessage("ERR: cluster id is different for cluster " + clusterName + ": " + cluster2Id + " <-> " + cluster1Id); + ++differences; + ok = false; + } + + long countCluster1 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Long call(ODatabaseDocumentInternal database) { + return database.getStorage().count(cluster1Id); + } + }); + long countCluster2 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Long call(ODatabaseDocumentInternal database) { + return database.getStorage().count(cluster2Id); + } + }); + + if (countCluster1 != countCluster2) { + listener.onMessage( + "ERR: number of records different in cluster '" + clusterName + "' (id=" + cluster1Id + "): " + countCluster1 + " <-> " + + countCluster2); + ++differences; + ok = false; + } + + if (ok) + listener.onMessage("OK"); + } + + listener.onMessage("\n\nShallow analysis done."); + return true; + } + + @SuppressFBWarnings("NP_NULL_ON_SOME_PATH") + private boolean compareRecords(ODocumentHelper.RIDMapper ridMapper) { + listener.onMessage("\nStarting deep comparison record by record. This may take a few minutes. Wait please..."); + + Collection clusterNames1 = makeDbCall(databaseOne, new ODbRelatedCall>() { + @Override + public Collection call(ODatabaseDocumentInternal database) { + return database.getClusterNames(); + } + }); + + for (final String clusterName : clusterNames1) { + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(clusterName)) + continue; + } else if (excludeClusters != null) { + if (excludeClusters.contains(clusterName)) + continue; + } + + final int clusterId1 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public Integer call(ODatabaseDocumentInternal database) { + return database.getClusterIdByName(clusterName); + } + }); + + final long[] db1Range = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public long[] call(ODatabaseDocumentInternal database) { + return database.getStorage().getClusterDataRange(clusterId1); + } + }); + final long[] db2Range = makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public long[] call(ODatabaseDocumentInternal database) { + return database.getStorage().getClusterDataRange(clusterId1); + } + }); + + final long db1Max = db1Range[1]; + final long db2Max = db2Range[1]; + + databaseOne.activateOnCurrentThread(); + final ODocument doc1 = new ODocument(); + databaseTwo.activateOnCurrentThread(); + final ODocument doc2 = new ODocument(); + + final ORecordId rid = new ORecordId(clusterId1); + + // TODO why this maximums can be different? + final long clusterMax = Math.max(db1Max, db2Max); + + final OStorage storage; + + ODatabaseDocumentInternal selectedDatabase; + if (clusterMax == db1Max) + selectedDatabase = databaseOne; + else + selectedDatabase = databaseTwo; + + OPhysicalPosition[] physicalPositions = makeDbCall(selectedDatabase, new ODbRelatedCall() { + @Override + public OPhysicalPosition[] call(ODatabaseDocumentInternal database) { + return database.getStorage().ceilingPhysicalPositions(clusterId1, new OPhysicalPosition(0)); + } + }); + + OStorageConfiguration configuration1 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public OStorageConfiguration call(ODatabaseDocumentInternal database) { + return database.getStorage().getConfiguration(); + } + }); + OStorageConfiguration configuration2 = makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public OStorageConfiguration call(ODatabaseDocumentInternal database) { + return database.getStorage().getConfiguration(); + } + }); + String storageType1 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public String call(ODatabaseDocumentInternal database) { + return database.getStorage().getType(); + } + }); + String storageType2 = makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public String call(ODatabaseDocumentInternal database) { + return database.getStorage().getType(); + } + }); + + long recordsCounter = 0; + while (physicalPositions.length > 0) { + for (OPhysicalPosition physicalPosition : physicalPositions) { + try { + recordsCounter++; + + final long position = physicalPosition.clusterPosition; + rid.setClusterPosition(position); + + if (rid.equals(new ORecordId(configuration1.indexMgrRecordId)) && rid + .equals(new ORecordId(configuration2.indexMgrRecordId))) + continue; + if (rid.equals(new ORecordId(configuration1.schemaRecordId)) && rid + .equals(new ORecordId(configuration2.schemaRecordId))) + continue; + + if (rid.getClusterId() == 0 && rid.getClusterPosition() == 0) { + // Skip the compare of raw structure if the storage type are different, due the fact that are different by definition. + if (!storageType1.equals(storageType2)) + continue; + } + + final ORecordId rid2; + if (ridMapper == null) + rid2 = rid; + else { + final ORID newRid = ridMapper.map(rid); + if (newRid == null) + rid2 = rid; + else + rid2 = new ORecordId(newRid); + } + + final ORawBuffer buffer1 = makeDbCall(databaseOne, new ODbRelatedCall() { + @Override + public ORawBuffer call(ODatabaseDocumentInternal database) { + return database.getStorage().readRecord(rid, null, true, false, null).getResult(); + } + }); + final ORawBuffer buffer2 = makeDbCall(databaseTwo, new ODbRelatedCall() { + @Override + public ORawBuffer call(ODatabaseDocumentInternal database) { + return database.getStorage().readRecord(rid2, null, true, false, null).getResult(); + } + }); + + if (buffer1 == null && buffer2 == null) + // BOTH RECORD NULL, OK + continue; + else if (buffer1 == null && buffer2 != null) { + // REC1 NULL + listener.onMessage("\n- ERR: RID=" + clusterId1 + ":" + position + " is null in DB1"); + ++differences; + } else if (buffer1 != null && buffer2 == null) { + // REC2 NULL + listener.onMessage("\n- ERR: RID=" + clusterId1 + ":" + position + " is null in DB2"); + ++differences; + } else { + if (buffer1.recordType != buffer2.recordType) { + listener.onMessage( + "\n- ERR: RID=" + clusterId1 + ":" + position + " recordType is different: " + (char) buffer1.recordType + + " <-> " + (char) buffer2.recordType); + ++differences; + } + + if (buffer1.buffer == null && buffer2.buffer == null) { + } else if (buffer1.buffer == null && buffer2.buffer != null) { + listener.onMessage( + "\n- ERR: RID=" + clusterId1 + ":" + position + " content is different: null <-> " + buffer2.buffer.length); + ++differences; + + } else if (buffer1.buffer != null && buffer2.buffer == null) { + listener.onMessage("\n- ERR: RID=" + clusterId1 + ":" + position + " content is different: " + buffer1.buffer.length + + " <-> null"); + ++differences; + + } else { + if (buffer1.recordType == ODocument.RECORD_TYPE) { + // DOCUMENT: TRY TO INSTANTIATE AND COMPARE + + makeDbCall(databaseOne, new ODocumentHelper.ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + doc1.reset(); + doc1.fromStream(buffer1.buffer); + return null; + } + }); + + makeDbCall(databaseTwo, new ODocumentHelper.ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + doc2.reset(); + doc2.fromStream(buffer2.buffer); + return null; + } + }); + + if (rid.toString().equals(configuration1.schemaRecordId) && rid.toString() + .equals(configuration2.schemaRecordId)) { + makeDbCall(databaseOne, new ODocumentHelper.ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + convertSchemaDoc(doc1); + return null; + } + }); + + makeDbCall(databaseTwo, new ODocumentHelper.ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + convertSchemaDoc(doc2); + return null; + } + }); + } + + if (!ODocumentHelper.hasSameContentOf(doc1, databaseOne, doc2, databaseTwo, ridMapper)) { + listener.onMessage("\n- ERR: RID=" + clusterId1 + ":" + position + " document content is different"); + listener.onMessage("\n--- REC1: " + new String(buffer1.buffer)); + listener.onMessage("\n--- REC2: " + new String(buffer2.buffer)); + listener.onMessage("\n"); + ++differences; + } + } else { + if (buffer1.buffer.length != buffer2.buffer.length) { + // CHECK IF THE TRIMMED SIZE IS THE SAME + final String rec1 = new String(buffer1.buffer).trim(); + final String rec2 = new String(buffer2.buffer).trim(); + + if (rec1.length() != rec2.length()) { + listener.onMessage( + "\n- ERR: RID=" + clusterId1 + ":" + position + " content length is different: " + buffer1.buffer.length + + " <-> " + buffer2.buffer.length); + + if (buffer1.recordType == ODocument.RECORD_TYPE) + listener.onMessage("\n--- REC1: " + rec1); + if (buffer2.recordType == ODocument.RECORD_TYPE) + listener.onMessage("\n--- REC2: " + rec2); + listener.onMessage("\n"); + + ++differences; + } + } else { + // CHECK BYTE PER BYTE + for (int b = 0; b < buffer1.buffer.length; ++b) { + if (buffer1.buffer[b] != buffer2.buffer[b]) { + listener.onMessage( + "\n- ERR: RID=" + clusterId1 + ":" + position + " content is different at byte #" + b + ": " + + buffer1.buffer[b] + " <-> " + buffer2.buffer[b]); + listener.onMessage("\n--- REC1: " + new String(buffer1.buffer)); + listener.onMessage("\n--- REC2: " + new String(buffer2.buffer)); + listener.onMessage("\n"); + ++differences; + break; + } + } + } + } + } + } + } catch (RuntimeException e) { + OLogManager.instance().error(this, "Error during data comparison of records with rid " + rid); + throw e; + } + } + final OPhysicalPosition[] curPosition = physicalPositions; + physicalPositions = makeDbCall(selectedDatabase, new ODbRelatedCall() { + @Override + public OPhysicalPosition[] call(ODatabaseDocumentInternal database) { + return database.getStorage().higherPhysicalPositions(clusterId1, curPosition[curPosition.length - 1]); + } + }); + if (recordsCounter % 10000 == 0) + listener.onMessage("\n" + recordsCounter + " records were processed for cluster " + clusterName + " ..."); + } + + listener.onMessage( + "\nCluster comparison was finished, " + recordsCounter + " records were processed for cluster " + clusterName + " ..."); + } + + return true; + } + + public void setCompareIndexMetadata(boolean compareIndexMetadata) { + this.compareIndexMetadata = compareIndexMetadata; + } + + public boolean isCompareEntriesForAutomaticIndexes() { + return compareEntriesForAutomaticIndexes; + } + + public void setCompareEntriesForAutomaticIndexes(boolean compareEntriesForAutomaticIndexes) { + this.compareEntriesForAutomaticIndexes = compareEntriesForAutomaticIndexes; + } + + public void setAutoDetectExportImportMap(boolean autoDetectExportImportMap) { + this.autoDetectExportImportMap = autoDetectExportImportMap; + } + + private void convertSchemaDoc(final ODocument document) { + if (document.field("classes") != null) { + document.setFieldType("classes", OType.EMBEDDEDSET); + for (ODocument classDoc : document.>field("classes")) { + classDoc.setFieldType("properties", OType.EMBEDDEDSET); + } + } + } + + private void reportIndexDiff(OIndex indexOne, Object key, final Object indexOneValue, final Object indexTwoValue) { + listener.onMessage("\n- ERR: Entry values for key '" + key + "' are different for index " + indexOne.getName()); + listener.onMessage("\n--- DB1: " + makeDbCall(databaseOne, new ODbRelatedCall() { + public String call(ODatabaseDocumentInternal database) { + return indexOneValue.toString(); + } + })); + listener.onMessage("\n--- DB2: " + makeDbCall(databaseOne, new ODbRelatedCall() { + public String call(ODatabaseDocumentInternal database) { + return indexTwoValue.toString(); + } + })); + listener.onMessage("\n"); + ++differences; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExport.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExport.java new file mode 100755 index 00000000000..ca406b7d4c4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExport.java @@ -0,0 +1,619 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import com.orientechnologies.common.io.OIOException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.OConstants; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexManagerProxy; +import com.orientechnologies.orient.core.index.ORuntimeKeyIndexDefinition; +import com.orientechnologies.orient.core.iterator.ORecordIteratorCluster; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.metadata.schema.OSchemaShared; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OJSONWriter; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.io.*; +import java.util.*; +import java.util.zip.Deflater; +import java.util.zip.GZIPOutputStream; + +/** + * Export data from a database to a file. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ODatabaseExport extends ODatabaseImpExpAbstract { + public static final int VERSION = 12; + + protected OJSONWriter writer; + protected long recordExported; + protected int compressionLevel = Deflater.BEST_SPEED; + protected int compressionBuffer = 16384; // 16Kb + + public ODatabaseExport(final ODatabaseDocumentInternal iDatabase, final String iFileName, final OCommandOutputListener iListener) + throws IOException { + super(iDatabase, iFileName, iListener); + + if (fileName == null) + throw new IllegalArgumentException("file name missing"); + + if (!fileName.endsWith(".gz")) { + fileName += ".gz"; + } + final File f = new File(fileName); + if (f.getParentFile() != null) + f.getParentFile().mkdirs(); + if (f.exists()) + f.delete(); + + final GZIPOutputStream gzipOS = new GZIPOutputStream(new FileOutputStream(fileName), compressionBuffer) { + { + def.setLevel(compressionLevel); + } + }; + + writer = new OJSONWriter(new OutputStreamWriter(gzipOS)); + writer.beginObject(); + } + + public ODatabaseExport(final ODatabaseDocumentInternal iDatabase, final OutputStream iOutputStream, + final OCommandOutputListener iListener) throws IOException { + super(iDatabase, "streaming", iListener); + + writer = new OJSONWriter(new OutputStreamWriter(iOutputStream)); + writer.beginObject(); + } + + @Override + public void run() { + exportDatabase(); + } + + @Override + public ODatabaseExport setOptions(final String s) { + super.setOptions(s); + return this; + } + + public ODatabaseExport exportDatabase() { + try { + listener.onMessage("\nStarted export of database '" + database.getName() + "' to " + fileName + "..."); + + long time = System.currentTimeMillis(); + + if (includeInfo) + exportInfo(); + if (includeClusterDefinitions) + exportClusters(); + if (includeSchema) + exportSchema(); + if (includeRecords) + exportRecords(); + if (includeIndexDefinitions) + exportIndexDefinitions(); + if (includeManualIndexes) + exportManualIndexes(); + + listener.onMessage("\n\nDatabase export completed in " + (System.currentTimeMillis() - time) + "ms"); + + writer.flush(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on exporting database '%s' to: %s", e, database.getName(), fileName); + throw new ODatabaseExportException("Error on exporting database '" + database.getName() + "' to: " + fileName, e); + } finally { + close(); + } + return this; + } + + public long exportRecords() throws IOException { + long totalFoundRecords = 0; + long totalExportedRecords = 0; + + int level = 1; + listener.onMessage("\nExporting records..."); + + final Set brokenRids = new HashSet(); + + writer.beginCollection(level, true, "records"); + int exportedClusters = 0; + int maxClusterId = getMaxClusterId(); + for (int i = 0; exportedClusters <= maxClusterId; ++i) { + String clusterName = database.getClusterNameById(i); + + exportedClusters++; + + long clusterExportedRecordsTot = 0; + + if (clusterName != null) { + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(clusterName.toUpperCase(Locale.ENGLISH))) + continue; + } else if (excludeClusters != null) { + if (excludeClusters.contains(clusterName.toUpperCase(Locale.ENGLISH))) + continue; + } + + if (excludeClusters != null && excludeClusters.contains(clusterName.toUpperCase(Locale.ENGLISH))) + continue; + + clusterExportedRecordsTot = database.countClusterElements(clusterName); + } else if (includeClusters != null && !includeClusters.isEmpty()) + continue; + + listener.onMessage("\n- Cluster " + (clusterName != null ? "'" + clusterName + "'" : "NULL") + " (id=" + i + ")..."); + + long clusterExportedRecordsCurrent = 0; + + if (clusterName != null) { + ORecord rec = null; + try { + ORecordIteratorCluster it = database.browseCluster(clusterName); + + for (; it.hasNext(); ) { + + rec = it.next(); + if (rec instanceof ODocument) { + // CHECK IF THE CLASS OF THE DOCUMENT IS INCLUDED + ODocument doc = (ODocument) rec; + final String className = doc.getClassName() != null ? doc.getClassName().toUpperCase(Locale.ENGLISH) : null; + if (includeClasses != null) { + if (!includeClasses.contains(className)) + continue; + } else if (excludeClasses != null) { + if (excludeClasses.contains(className)) + continue; + } + } else if (includeClasses != null && !includeClasses.isEmpty()) + continue; + + if (exportRecord(clusterExportedRecordsTot, clusterExportedRecordsCurrent, rec, brokenRids)) + clusterExportedRecordsCurrent++; + } + + brokenRids.addAll(it.getBrokenRIDs()); + } catch (IOException e) { + OLogManager.instance().error(this, "\nError on exporting record %s because of I/O problems", e, rec.getIdentity()); + // RE-THROW THE EXCEPTION UP + throw e; + } catch (OIOException e) { + OLogManager.instance() + .error(this, "\nError on exporting record %s because of I/O problems", e, rec == null ? null : rec.getIdentity()); + // RE-THROW THE EXCEPTION UP + throw e; + } catch (Throwable t) { + if (rec != null) { + final byte[] buffer = rec.toStream(); + + OLogManager.instance().error(this, + "\nError on exporting record %s. It seems corrupted; size: %d bytes, raw content (as string):\n==========\n%s\n==========", + t, rec.getIdentity(), buffer.length, new String(buffer)); + } + } + } + + listener.onMessage("OK (records=" + clusterExportedRecordsCurrent + "/" + clusterExportedRecordsTot + ")"); + + totalExportedRecords += clusterExportedRecordsCurrent; + totalFoundRecords += clusterExportedRecordsTot; + } + writer.endCollection(level, true); + + listener.onMessage( + "\n\nDone. Exported " + totalExportedRecords + " of total " + totalFoundRecords + " records. " + brokenRids.size() + + " records were detected as broken\n"); + + writer.beginCollection(level, true, "brokenRids"); + + boolean firsBrokenRid = true; + + for (ORID rid : brokenRids) { + if (firsBrokenRid) + firsBrokenRid = false; + else + writer.append(","); + + writer.append(rid.toString()); + } + + writer.endCollection(level, true); + + return totalExportedRecords; + } + + public void close() { + database.declareIntent(null); + + if (writer == null) + return; + + try { + writer.endObject(); + writer.close(); + writer = null; + } catch (IOException e) { + } + } + + protected int getMaxClusterId() { + int totalCluster = -1; + for (String clusterName : database.getClusterNames()) { + if (database.getClusterIdByName(clusterName) > totalCluster) + totalCluster = database.getClusterIdByName(clusterName); + } + return totalCluster; + } + + @Override + protected void parseSetting(final String option, final List items) { + if (option.equalsIgnoreCase("-compressionLevel")) + compressionLevel = Integer.parseInt(items.get(0)); + else if (option.equalsIgnoreCase("-compressionBuffer")) + compressionBuffer = Integer.parseInt(items.get(0)); + else + super.parseSetting(option, items); + } + + private void exportClusters() throws IOException { + listener.onMessage("\nExporting clusters..."); + + writer.beginCollection(1, true, "clusters"); + int exportedClusters = 0; + + int maxClusterId = getMaxClusterId(); + + for (int clusterId = 0; clusterId <= maxClusterId; ++clusterId) { + + final String clusterName = database.getClusterNameById(clusterId); + + // exclude removed clusters + if (clusterName == null) + continue; + + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(clusterName.toUpperCase(Locale.ENGLISH))) + continue; + } else if (excludeClusters != null) { + if (excludeClusters.contains(clusterName.toUpperCase(Locale.ENGLISH))) + continue; + } + + writer.beginObject(2, true, null); + + writer.writeAttribute(0, false, "name", clusterName); + writer.writeAttribute(0, false, "id", clusterId); + + exportedClusters++; + writer.endObject(2, false); + } + + listener.onMessage("OK (" + exportedClusters + " clusters)"); + + writer.endCollection(1, true); + } + + private void exportInfo() throws IOException { + listener.onMessage("\nExporting database info..."); + + writer.beginObject(1, true, "info"); + writer.writeAttribute(2, true, "name", database.getName().replace('\\', '/')); + writer.writeAttribute(2, true, "default-cluster-id", database.getDefaultClusterId()); + writer.writeAttribute(2, true, "exporter-version", VERSION); + writer.writeAttribute(2, true, "engine-version", OConstants.ORIENT_VERSION); + final String engineBuild = OConstants.getBuildNumber(); + if (engineBuild != null) + writer.writeAttribute(2, true, "engine-build", engineBuild); + writer.writeAttribute(2, true, "storage-config-version", OStorageConfiguration.CURRENT_VERSION); + writer.writeAttribute(2, true, "schema-version", OSchemaShared.CURRENT_VERSION_NUMBER); + writer.writeAttribute(2, true, "schemaRecordId", database.getStorage().getConfiguration().schemaRecordId); + writer.writeAttribute(2, true, "indexMgrRecordId", database.getStorage().getConfiguration().indexMgrRecordId); + writer.endObject(1, true); + + listener.onMessage("OK"); + } + + private void exportIndexDefinitions() throws IOException { + listener.onMessage("\nExporting index info..."); + writer.beginCollection(1, true, "indexes"); + + final OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + indexManager.reload(); + + final Collection> indexes = indexManager.getIndexes(); + + for (OIndex index : indexes) { + if (index.getName().equals(ODatabaseImport.EXPORT_IMPORT_MAP_NAME)) + continue; + + final String clsName = index.getDefinition() != null ? index.getDefinition().getClassName() : null; + + // CHECK TO FILTER CLASS + if (includeClasses != null) { + if (!includeClasses.contains(clsName)) + continue; + } else if (excludeClasses != null) { + if (excludeClasses.contains(clsName)) + continue; + } + + listener.onMessage("\n- Index " + index.getName() + "..."); + writer.beginObject(2, true, null); + writer.writeAttribute(3, true, "name", index.getName()); + writer.writeAttribute(3, true, "type", index.getType()); + if (index.getAlgorithm() != null) + writer.writeAttribute(3, true, "algorithm", index.getAlgorithm()); + + if (!index.getClusters().isEmpty()) + writer.writeAttribute(3, true, "clustersToIndex", index.getClusters()); + + if (index.getDefinition() != null) { + writer.beginObject(4, true, "definition"); + + writer.writeAttribute(5, true, "defClass", index.getDefinition().getClass().getName()); + writer.writeAttribute(5, true, "stream", index.getDefinition().toStream()); + + writer.endObject(4, true); + } + + final ODocument metadata = index.getMetadata(); + if (metadata != null) + writer.writeAttribute(4, true, "metadata", metadata); + + final ODocument configuration = index.getConfiguration(); + if (configuration.field("blueprintsIndexClass") != null) + writer.writeAttribute(4, true, "blueprintsIndexClass", configuration.field("blueprintsIndexClass")); + + writer.endObject(2, true); + listener.onMessage("OK"); + } + + writer.endCollection(1, true); + listener.onMessage("\nOK (" + indexes.size() + " indexes)"); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void exportManualIndexes() throws IOException { + listener.onMessage("\nExporting manual indexes content..."); + + final OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + indexManager.reload(); + + final Collection> indexes = indexManager.getIndexes(); + + ODocument exportEntry = new ODocument(); + + int manualIndexes = 0; + writer.beginCollection(1, true, "manualIndexes"); + for (OIndex index : indexes) { + if (index.getName().equals(ODatabaseImport.EXPORT_IMPORT_MAP_NAME)) + continue; + + if (!index.isAutomatic()) { + listener.onMessage("\n- Exporting index " + index.getName() + " ..."); + + writer.beginObject(2, true, null); + writer.writeAttribute(3, true, "name", index.getName()); + + List indexContent = database.query(new OSQLSynchQuery("select from index:" + index.getName())); + + writer.beginCollection(3, true, "content"); + + int i = 0; + for (ODocument indexEntry : indexContent) { + if (i > 0) + writer.append(","); + + indexEntry.setLazyLoad(false); + final OIndexDefinition indexDefinition = index.getDefinition(); + + exportEntry.reset(); + exportEntry.setLazyLoad(false); + + if (indexDefinition instanceof ORuntimeKeyIndexDefinition + && ((ORuntimeKeyIndexDefinition) indexDefinition).getSerializer() != null) { + final OBinarySerializer binarySerializer = ((ORuntimeKeyIndexDefinition) indexDefinition).getSerializer(); + + final int dataSize = binarySerializer.getObjectSize(indexEntry.field("key")); + final byte[] binaryContent = new byte[dataSize]; + binarySerializer.serialize(indexEntry.field("key"), binaryContent, 0); + + exportEntry.field("binary", true); + exportEntry.field("key", binaryContent); + } else { + exportEntry.field("binary", false); + exportEntry.field("key", indexEntry.field("key")); + } + + exportEntry.field("rid", indexEntry.field("rid")); + + i++; + + writer.append(exportEntry.toJSON()); + + final long percent = indexContent.size() / 10; + if (percent > 0 && (i % percent) == 0) + listener.onMessage("."); + } + writer.endCollection(3, true); + + writer.endObject(2, true); + listener.onMessage("OK (entries=" + index.getSize() + ")"); + manualIndexes++; + } + } + writer.endCollection(1, true); + listener.onMessage("\nOK (" + manualIndexes + " manual indexes)"); + } + + private void exportSchema() throws IOException { + listener.onMessage("\nExporting schema..."); + + writer.beginObject(1, true, "schema"); + OSchema s = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot(); + writer.writeAttribute(2, true, "version", s.getVersion()); + writer.writeAttribute(2, false, "blob-clusters", database.getBlobClusterIds()); + if (!s.getClasses().isEmpty()) { + writer.beginCollection(2, true, "classes"); + + final List classes = new ArrayList(s.getClasses()); + Collections.sort(classes); + + for (OClass cls : classes) { + // CHECK TO FILTER CLASS + if (includeClasses != null) { + if (!includeClasses.contains(cls.getName().toUpperCase(Locale.ENGLISH))) + continue; + } else if (excludeClasses != null) { + if (excludeClasses.contains(cls.getName().toUpperCase(Locale.ENGLISH))) + continue; + } + + writer.beginObject(3, true, null); + writer.writeAttribute(0, false, "name", cls.getName()); + writer.writeAttribute(0, false, "default-cluster-id", cls.getDefaultClusterId()); + writer.writeAttribute(0, false, "cluster-ids", cls.getClusterIds()); + if (cls.getOverSize() > 1) + writer.writeAttribute(0, false, "oversize", cls.getClassOverSize()); + if (cls.isStrictMode()) + writer.writeAttribute(0, false, "strictMode", cls.isStrictMode()); + if (!cls.getSuperClasses().isEmpty()) + writer.writeAttribute(0, false, "super-classes", cls.getSuperClassesNames()); + if (cls.getShortName() != null) + writer.writeAttribute(0, false, "short-name", cls.getShortName()); + if (cls.isAbstract()) + writer.writeAttribute(0, false, "abstract", cls.isAbstract()); + writer.writeAttribute(0, false, "cluster-selection", cls.getClusterSelection().getName()); // @SINCE 1.7 + + if (!cls.properties().isEmpty()) { + writer.beginCollection(4, true, "properties"); + + final List properties = new ArrayList(cls.declaredProperties()); + Collections.sort(properties); + + for (OProperty p : properties) { + writer.beginObject(5, true, null); + writer.writeAttribute(0, false, "name", p.getName()); + writer.writeAttribute(0, false, "type", p.getType().toString()); + if (p.isMandatory()) + writer.writeAttribute(0, false, "mandatory", p.isMandatory()); + if (p.isReadonly()) + writer.writeAttribute(0, false, "readonly", p.isReadonly()); + if (p.isNotNull()) + writer.writeAttribute(0, false, "not-null", p.isNotNull()); + if (p.getLinkedClass() != null) + writer.writeAttribute(0, false, "linked-class", p.getLinkedClass().getName()); + if (p.getLinkedType() != null) + writer.writeAttribute(0, false, "linked-type", p.getLinkedType().toString()); + if (p.getMin() != null) + writer.writeAttribute(0, false, "min", p.getMin()); + if (p.getMax() != null) + writer.writeAttribute(0, false, "max", p.getMax()); + if (p.getCollate() != null) + writer.writeAttribute(0, false, "collate", p.getCollate().getName()); + if (p.getDefaultValue() != null) + writer.writeAttribute(0, false, "default-value", p.getDefaultValue()); + if (p.getRegexp() != null) + writer.writeAttribute(0, false, "regexp", p.getRegexp()); + final Set customKeys = p.getCustomKeys(); + final Map custom = new HashMap(); + for (String key : customKeys) + custom.put(key, p.getCustom(key)); + + if (!custom.isEmpty()) + writer.writeAttribute(0, false, "customFields", custom); + + writer.endObject(0, false); + } + writer.endCollection(4, true); + } + final Set customKeys = cls.getCustomKeys(); + final Map custom = new HashMap(); + for (String key : customKeys) + custom.put(key, cls.getCustom(key)); + + if (!custom.isEmpty()) + writer.writeAttribute(0, false, "customFields", custom); + + writer.endObject(3, true); + } + writer.endCollection(2, true); + } + + writer.endObject(1, true); + + listener.onMessage("OK (" + s.getClasses().size() + " classes)"); + } + + private boolean exportRecord(long recordTot, long recordNum, ORecord rec, Set brokenRids) throws IOException { + if (rec != null) + try { + if (rec.getIdentity().isValid()) + rec.reload(); + + if (useLineFeedForRecords) + writer.append("\n"); + + if (recordExported > 0) + writer.append(","); + + writer.append(rec.toJSON("rid,type,version,class,attribSameRow,keepTypes,alwaysFetchEmbedded,dateAsLong")); + + recordExported++; + recordNum++; + + if (recordTot > 10 && (recordNum + 1) % (recordTot / 10) == 0) + listener.onMessage("."); + + return true; + } catch (Throwable t) { + if (rec != null) { + final ORID rid = rec.getIdentity().copy(); + + if (rid != null) { + brokenRids.add(rid); + } + + final byte[] buffer = rec.toStream(); + + OLogManager.instance().error(this, + "\nError on exporting record %s. It seems corrupted; size: %d bytes, raw content (as string):\n==========\n%s\n==========", + t, rec.getIdentity(), buffer.length, new String(buffer)); + } + } + + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExportException.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExportException.java new file mode 100644 index 00000000000..e46d7ddcd01 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseExportException.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.tool; + +@SuppressWarnings("serial") +public class ODatabaseExportException extends RuntimeException { + + public ODatabaseExportException() { + super(); + } + + public ODatabaseExportException(String message, Throwable cause) { + super(message, cause); + } + + public ODatabaseExportException(String message) { + super(message); + } + + public ODatabaseExportException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImpExpAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImpExpAbstract.java new file mode 100644 index 00000000000..19472ba6fcc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImpExpAbstract.java @@ -0,0 +1,258 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; + +import java.util.*; + +/** + * Abstract class for import/export of database and data in general. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class ODatabaseImpExpAbstract extends ODatabaseTool { + protected final static String DEFAULT_EXT = ".json"; + protected ODatabaseDocumentInternal database; + protected String fileName; + protected Set includeClusters; + protected Set excludeClusters; + protected Set includeClasses; + protected Set excludeClasses; + protected boolean includeInfo = true; + protected boolean includeClusterDefinitions = true; + protected boolean includeSchema = true; + protected boolean includeSecurity = false; + protected boolean includeRecords = true; + protected boolean includeIndexDefinitions = true; + protected boolean includeManualIndexes = true; + protected boolean useLineFeedForRecords = false; + protected boolean preserveRids = false; + protected OCommandOutputListener listener; + + public ODatabaseImpExpAbstract(final ODatabaseDocumentInternal iDatabase, final String iFileName, + final OCommandOutputListener iListener) { + database = iDatabase; + fileName = iFileName; + + // Fix bug where you can't backup files with spaces. Now you can wrap with quotes and the filesystem won't create + // directories with quotes in their name. + if (fileName != null) { + if ((fileName.startsWith("\"") && fileName.endsWith("\"")) || (fileName.startsWith("'") && fileName.endsWith("'"))) { + fileName = fileName.substring(1, fileName.length() - 1); + iListener.onMessage("Detected quotes surrounding filename; new backup file: " + fileName); + } + } + + if (fileName != null && fileName.indexOf('.') == -1) + fileName += DEFAULT_EXT; + + listener = iListener; + excludeClusters = new LinkedHashSet(); + excludeClusters.add(OMetadataDefault.CLUSTER_INDEX_NAME); + excludeClusters.add(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME); + } + + public Set getIncludeClusters() { + return includeClusters; + } + + public void setIncludeClusters(final Set includeClusters) { + this.includeClusters = includeClusters; + } + + public Set getExcludeClusters() { + return excludeClusters; + } + + public void setExcludeClusters(final Set excludeClusters) { + this.excludeClusters = excludeClusters; + } + + public Set getIncludeClasses() { + return includeClasses; + } + + public void setIncludeClasses(final Set includeClasses) { + this.includeClasses = includeClasses; + } + + public Set getExcludeClasses() { + return excludeClasses; + } + + public void setExcludeClasses(final Set excludeClasses) { + this.excludeClasses = excludeClasses; + } + + public OCommandOutputListener getListener() { + return listener; + } + + public void setListener(final OCommandOutputListener listener) { + this.listener = listener; + } + + public ODatabaseDocument getDatabase() { + return database; + } + + public String getFileName() { + return fileName; + } + + public boolean isIncludeInfo() { + return includeInfo; + } + + public void setIncludeInfo(final boolean includeInfo) { + this.includeInfo = includeInfo; + } + + public boolean isIncludeSecurity() { + return includeSecurity; + } + + public void setIncludeSecurity(final boolean includeSecurity) { + this.includeSecurity = includeSecurity; + } + + public boolean isIncludeSchema() { + return includeSchema; + } + + public void setIncludeSchema(final boolean includeSchema) { + this.includeSchema = includeSchema; + } + + public boolean isIncludeRecords() { + return includeRecords; + } + + public void setIncludeRecords(final boolean includeRecords) { + this.includeRecords = includeRecords; + } + + public boolean isIncludeIndexDefinitions() { + return includeIndexDefinitions; + } + + public void setIncludeIndexDefinitions(final boolean includeIndexDefinitions) { + this.includeIndexDefinitions = includeIndexDefinitions; + } + + public boolean isIncludeManualIndexes() { + return includeManualIndexes; + } + + public void setIncludeManualIndexes(final boolean includeManualIndexes) { + this.includeManualIndexes = includeManualIndexes; + } + + public boolean isIncludeClusterDefinitions() { + return includeClusterDefinitions; + } + + public void setIncludeClusterDefinitions(final boolean includeClusterDefinitions) { + this.includeClusterDefinitions = includeClusterDefinitions; + } + + public boolean isUseLineFeedForRecords() { + return useLineFeedForRecords; + } + + public void setUseLineFeedForRecords(final boolean useLineFeedForRecords) { + this.useLineFeedForRecords = useLineFeedForRecords; + } + + public boolean isPreserveRids() { + return preserveRids; + } + + public void setPreserveRids(boolean preserveRids) { + this.preserveRids = preserveRids; + } + + protected void parseSetting(final String option, final List items) { + if (option.equalsIgnoreCase("-excludeAll")) { + includeInfo = false; + includeClusterDefinitions = false; + includeSchema = false; + includeSecurity = false; + includeRecords = false; + includeIndexDefinitions = false; + includeManualIndexes = false; + + } else if (option.equalsIgnoreCase("-includeClass")) { + includeClasses = new HashSet(); + for (String item : items) + includeClasses.add(item.toUpperCase(Locale.ENGLISH)); + includeRecords = true; + + } else if (option.equalsIgnoreCase("-excludeClass")) { + excludeClasses = new HashSet(items); + for (String item : items) + excludeClasses.add(item.toUpperCase(Locale.ENGLISH)); + + } else if (option.equalsIgnoreCase("-includeCluster")) { + includeClusters = new HashSet(items); + for (String item : items) + includeClusters.add(item.toUpperCase(Locale.ENGLISH)); + includeRecords = true; + + } else if (option.equalsIgnoreCase("-excludeCluster")) { + excludeClusters = new HashSet(items); + for (String item : items) + excludeClusters.add(item.toUpperCase(Locale.ENGLISH)); + + } else if (option.equalsIgnoreCase("-includeInfo")) { + includeInfo = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-includeClusterDefinitions")) { + includeClusterDefinitions = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-includeSchema")) { + includeSchema = Boolean.parseBoolean(items.get(0)); + if (includeSchema) { + includeClusterDefinitions = true; + includeInfo = true; + } + } else if (option.equalsIgnoreCase("-includeSecurity")) { + includeSecurity = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-includeRecords")) { + includeRecords = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-includeIndexDefinitions")) { + includeIndexDefinitions = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-includeManualIndexes")) { + includeManualIndexes = Boolean.parseBoolean(items.get(0)); + + } else if (option.equalsIgnoreCase("-useLineFeedForRecords")) { + useLineFeedForRecords = Boolean.parseBoolean(items.get(0)); + + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImport.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImport.java new file mode 100755 index 00000000000..cb818c0288e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImport.java @@ -0,0 +1,1694 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.db.ODatabase.STATUS; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODocumentFieldVisitor; +import com.orientechnologies.orient.core.db.document.ODocumentFieldWalker; +import com.orientechnologies.orient.core.db.record.OClassTrigger; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.index.hashindex.local.OHashIndexFactory; +import com.orientechnologies.orient.core.index.hashindex.local.OMurmurHash3HashFunction; +import com.orientechnologies.orient.core.intent.OIntentMassiveInsert; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.schema.*; +import com.orientechnologies.orient.core.metadata.security.OIdentity; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OJSONReader; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerJSON; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.storage.OPhysicalPosition; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.text.ParseException; +import java.util.*; +import java.util.Map.Entry; +import java.util.zip.GZIPInputStream; + +/** + * Import data from a file into a database. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ODatabaseImport extends ODatabaseImpExpAbstract { + public static final String EXPORT_IMPORT_MAP_NAME = "___exportImportRIDMap"; + public static final int IMPORT_RECORD_DUMP_LAP_EVERY_MS = 5000; + + private Map linkedClasses = new HashMap(); + private Map> superClasses = new HashMap>(); + private OJSONReader jsonReader; + private ORecord record; + private boolean schemaImported = false; + private int exporterVersion = -1; + private ORID schemaRecordId; + private ORID indexMgrRecordId; + + private boolean deleteRIDMapping = true; + + private OIndex exportImportHashTable; + + private boolean preserveClusterIDs = true; + private boolean migrateLinks = true; + private boolean merge = false; + private boolean rebuildIndexes = true; + + private Set indexesToRebuild = new HashSet(); + private Map convertedClassNames = new HashMap(); + + private interface ValuesConverter { + T convert(T value); + } + + private static final class ConvertersFactory { + public static final ORID BROKEN_LINK = new ORecordId(-1, -42); + public static final ConvertersFactory INSTANCE = new ConvertersFactory(); + + public ValuesConverter getConverter(Object value) { + if (value instanceof Map) + return MapConverter.INSTANCE; + + if (value instanceof List) + return ListConverter.INSTANCE; + + if (value instanceof Set) + return SetConverter.INSTANCE; + + if (value instanceof ORidBag) + return RidBagConverter.INSTANCE; + + if (value instanceof OIdentifiable) + return LinkConverter.INSTANCE; + + return null; + } + } + + private static final class LinksRewriter implements ODocumentFieldVisitor { + @Override + public Object visitField(OType type, OType linkedType, Object value) { + boolean oldAutoConvertValue = false; + if (value instanceof ORecordLazyMultiValue) { + ORecordLazyMultiValue multiValue = (ORecordLazyMultiValue) value; + oldAutoConvertValue = multiValue.isAutoConvertToRecord(); + multiValue.setAutoConvertToRecord(false); + } + + final ValuesConverter valuesConverter = ConvertersFactory.INSTANCE.getConverter(value); + if (valuesConverter == null) + return value; + + final Object newValue = valuesConverter.convert(value); + + if (value instanceof ORecordLazyMultiValue) { + ORecordLazyMultiValue multiValue = (ORecordLazyMultiValue) value; + multiValue.setAutoConvertToRecord(oldAutoConvertValue); + } + + //this code intentionally uses == instead of equals, in such case we may distinguish rids which already contained in + //document and RID which is used to indicate broken record + if (newValue == ConvertersFactory.BROKEN_LINK) + return null; + + return newValue; + } + + @Override + public boolean goFurther(OType type, OType linkedType, Object value, Object newValue) { + return true; + } + + @Override + public boolean goDeeper(OType type, OType linkedType, Object value) { + return true; + } + + @Override + public boolean updateMode() { + return true; + } + + } + + private static abstract class AbstractCollectionConverter implements ValuesConverter { + interface ResultCallback { + void add(Object item); + } + + protected boolean convertSingleValue(final Object item, ResultCallback result, boolean updated) { + if (item == null) + return false; + + if (item instanceof OIdentifiable) { + final ValuesConverter converter = (ValuesConverter) ConvertersFactory.INSTANCE + .getConverter(item); + + final OIdentifiable newValue = converter.convert((OIdentifiable) item); + + //this code intentionally uses == instead of equals, in such case we may distinguish rids which already contained in + //document and RID which is used to indicate broken record + if (newValue != ConvertersFactory.BROKEN_LINK) + result.add(newValue); + + if (!newValue.equals(item)) + updated = true; + } else { + final ValuesConverter valuesConverter = ConvertersFactory.INSTANCE.getConverter(item.getClass()); + if (valuesConverter == null) + result.add(item); + else { + final Object newValue = valuesConverter.convert(item); + if (newValue != item) + updated = true; + + result.add(newValue); + } + } + + return updated; + } + } + + private static final class SetConverter extends AbstractCollectionConverter { + public static final SetConverter INSTANCE = new SetConverter(); + + @Override + public Set convert(Set value) { + boolean updated = false; + final Set result; + + result = new HashSet(); + + final ResultCallback callback = new ResultCallback() { + @Override + public void add(Object item) { + result.add(item); + } + }; + + for (Object item : value) + updated = convertSingleValue(item, callback, updated); + + if (updated) + return result; + + return value; + } + } + + private static final class ListConverter extends AbstractCollectionConverter { + public static final ListConverter INSTANCE = new ListConverter(); + + @Override + public List convert(List value) { + final List result = new ArrayList(); + + final ResultCallback callback = new ResultCallback() { + @Override + public void add(Object item) { + result.add(item); + } + }; + boolean updated = false; + + for (Object item : value) + updated = convertSingleValue(item, callback, updated); + + if (updated) + return result; + + return value; + } + } + + private static final class RidBagConverter extends AbstractCollectionConverter { + public static final RidBagConverter INSTANCE = new RidBagConverter(); + + @Override + public ORidBag convert(ORidBag value) { + final ORidBag result = new ORidBag(); + boolean updated = false; + final ResultCallback callback = new ResultCallback() { + @Override + public void add(Object item) { + result.add((OIdentifiable) item); + } + }; + + for (OIdentifiable identifiable : value) + updated = convertSingleValue(identifiable, callback, updated); + + if (updated) + return result; + + return value; + } + } + + private static final class MapConverter extends AbstractCollectionConverter { + public static final MapConverter INSTANCE = new MapConverter(); + + @Override + public Map convert(Map value) { + final HashMap result = new HashMap(); + boolean updated = false; + final class MapResultCallback implements ResultCallback { + private Object key; + + @Override + public void add(Object item) { + result.put(key, item); + } + + public void setKey(Object key) { + this.key = key; + } + } + + final MapResultCallback callback = new MapResultCallback(); + for (Map.Entry entry : (Iterable) value.entrySet()) { + callback.setKey(entry.getKey()); + updated = convertSingleValue(entry.getValue(), callback, updated); + } + if (updated) + return result; + + return value; + } + } + + private static final class LinkConverter implements ValuesConverter { + public static final LinkConverter INSTANCE = new LinkConverter(); + + private OIndex exportImportHashTable; + private Set brokenRids = new HashSet(); + + @Override + public OIdentifiable convert(OIdentifiable value) { + final ORID rid = value.getIdentity(); + if (!rid.isPersistent()) + return value; + + if (brokenRids.contains(rid)) + return ConvertersFactory.BROKEN_LINK; + + final OIdentifiable newRid = exportImportHashTable.get(rid); + if (newRid == null) + return value; + + return newRid.getIdentity(); + } + + public void setExportImportHashTable(OIndex exportImportHashTable) { + this.exportImportHashTable = exportImportHashTable; + } + + public void setBrokenRids(Set brokenRids) { + this.brokenRids = brokenRids; + } + } + + public ODatabaseImport(final ODatabaseDocumentInternal database, final String iFileName, final OCommandOutputListener iListener) + throws IOException { + super(database, iFileName, iListener); + + if (iListener == null) + listener = new OCommandOutputListener() { + @Override + public void onMessage(String iText) { + } + }; + + InputStream inStream; + final BufferedInputStream bf = new BufferedInputStream(new FileInputStream(fileName)); + bf.mark(1024); + try { + inStream = new GZIPInputStream(bf, 16384); // 16KB + } catch (Exception e) { + bf.reset(); + inStream = bf; + } + + OMurmurHash3HashFunction keyHashFunction = new OMurmurHash3HashFunction(); + keyHashFunction.setValueSerializer(OLinkSerializer.INSTANCE); + + jsonReader = new OJSONReader(new InputStreamReader(inStream)); + database.declareIntent(new OIntentMassiveInsert()); + } + + public ODatabaseImport(final ODatabaseDocumentInternal database, final InputStream iStream, + final OCommandOutputListener iListener) throws IOException { + super(database, "streaming", iListener); + jsonReader = new OJSONReader(new InputStreamReader(iStream)); + database.declareIntent(new OIntentMassiveInsert()); + } + + @Override + public ODatabaseImport setOptions(String iOptions) { + super.setOptions(iOptions); + return this; + } + + @Override + public void run() { + importDatabase(); + } + + public ODatabaseImport importDatabase() { + boolean preValidation = database.isValidationEnabled(); + try { + listener.onMessage("\nStarted import of database '" + database.getURL() + "' from " + fileName + "..."); + + long time = System.currentTimeMillis(); + + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + + database.setMVCC(false); + database.setValidationEnabled(false); + + database.setStatus(STATUS.IMPORTING); + + if (!merge) { + removeDefaultNonSecurityClasses(); + database.getMetadata().getIndexManager().reload(); + } + + for (OIndex index : database.getMetadata().getIndexManager().getIndexes()) { + if (index.isAutomatic()) + indexesToRebuild.add(index.getName().toLowerCase(Locale.ENGLISH)); + } + + String tag; + boolean clustersImported = false; + while (jsonReader.hasNext() && jsonReader.lastChar() != '}') { + tag = jsonReader.readString(OJSONReader.FIELD_ASSIGNMENT); + + if (tag.equals("info")) + importInfo(); + else if (tag.equals("clusters")) { + importClusters(); + clustersImported = true; + } else if (tag.equals("schema")) + importSchema(clustersImported); + else if (tag.equals("records")) + importRecords(); + else if (tag.equals("indexes")) + importIndexes(); + else if (tag.equals("manualIndexes")) + importManualIndexes(); + else + throw new ODatabaseImportException("Invalid format. Found unsupported tag '" + tag + "'"); + } + + if (rebuildIndexes) + rebuildIndexes(); + + // This is needed to insure functions loaded into an open + // in memory database are available after the import. + // see issue #5245 + database.getMetadata().reload(); + + database.getStorage().synch(); + database.setStatus(STATUS.OPEN); + + if (isDeleteRIDMapping()) + removeExportImportRIDsMap(); + + listener.onMessage("\n\nDatabase import completed in " + ((System.currentTimeMillis() - time)) + " ms"); + + } catch (Exception e) { + final StringWriter writer = new StringWriter(); + writer.append("Error on database import happened just before line " + jsonReader.getLineNumber() + ", column " + jsonReader + .getColumnNumber() + "\n"); + final PrintWriter printWriter = new PrintWriter(writer); + e.printStackTrace(printWriter); + printWriter.flush(); + + listener.onMessage(writer.toString()); + + try { + writer.close(); + } catch (IOException e1) { + throw new ODatabaseExportException("Error on importing database '" + database.getName() + "' from file: " + fileName, e1); + } + + throw new ODatabaseExportException("Error on importing database '" + database.getName() + "' from file: " + fileName, e); + } finally { + database.setValidationEnabled(preValidation); + close(); + } + + return this; + } + + public void rebuildIndexes() { + database.getMetadata().getIndexManager().reload(); + + OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + + listener.onMessage("\nRebuild of stale indexes..."); + for (String indexName : indexesToRebuild) { + + if (indexManager.getIndex(indexName) == null) { + listener.onMessage("\nIndex " + indexName + " is skipped because it is absent in imported DB."); + continue; + } + + listener.onMessage("\nStart rebuild index " + indexName); + database.command(new OCommandSQL("rebuild index " + indexName)).execute(); + listener.onMessage("\nRebuild of index " + indexName + " is completed."); + } + listener.onMessage("\nStale indexes were rebuilt..."); + } + + public ODatabaseImport removeExportImportRIDsMap() { + listener.onMessage("\nDeleting RID Mapping table..."); + if (exportImportHashTable != null) { + database.command(new OCommandSQL("drop index " + EXPORT_IMPORT_MAP_NAME)); + exportImportHashTable = null; + } + + listener.onMessage("OK\n"); + return this; + } + + public void close() { + database.declareIntent(null); + } + + public boolean isMigrateLinks() { + return migrateLinks; + } + + public void setMigrateLinks(boolean migrateLinks) { + this.migrateLinks = migrateLinks; + } + + public boolean isRebuildIndexes() { + return rebuildIndexes; + } + + public void setRebuildIndexes(boolean rebuildIndexes) { + this.rebuildIndexes = rebuildIndexes; + } + + public boolean isPreserveClusterIDs() { + return preserveClusterIDs; + } + + public void setPreserveClusterIDs(boolean preserveClusterIDs) { + this.preserveClusterIDs = preserveClusterIDs; + } + + public boolean isMerge() { + return merge; + } + + public void setMerge(boolean merge) { + this.merge = merge; + } + + public boolean isDeleteRIDMapping() { + return deleteRIDMapping; + } + + public void setDeleteRIDMapping(boolean deleteRIDMapping) { + this.deleteRIDMapping = deleteRIDMapping; + } + + @Override + protected void parseSetting(final String option, final List items) { + if (option.equalsIgnoreCase("-deleteRIDMapping")) + deleteRIDMapping = Boolean.parseBoolean(items.get(0)); + else if (option.equalsIgnoreCase("-preserveClusterIDs")) + preserveClusterIDs = Boolean.parseBoolean(items.get(0)); + else if (option.equalsIgnoreCase("-merge")) + merge = Boolean.parseBoolean(items.get(0)); + else if (option.equalsIgnoreCase("-migrateLinks")) + migrateLinks = Boolean.parseBoolean(items.get(0)); + else if (option.equalsIgnoreCase("-rebuildIndexes")) + rebuildIndexes = Boolean.parseBoolean(items.get(0)); + else + super.parseSetting(option, items); + } + + public void setOption(final String option, String value) { + parseSetting("-" + option, Arrays.asList(value)); + } + + protected void removeDefaultClusters() { + listener.onMessage( + "\nWARN: Exported database does not support manual index separation." + " Manual index cluster will be dropped."); + + // In v4 new cluster for manual indexes has been implemented. To keep database consistent we should shift back + // all clusters and recreate cluster for manual indexes in the end. + database.dropCluster(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME, true); + + final OSchema schema = database.getMetadata().getSchema(); + if (schema.existsClass(OUser.CLASS_NAME)) + schema.dropClass(OUser.CLASS_NAME); + if (schema.existsClass(ORole.CLASS_NAME)) + schema.dropClass(ORole.CLASS_NAME); + if (schema.existsClass(OSecurityShared.RESTRICTED_CLASSNAME)) + schema.dropClass(OSecurityShared.RESTRICTED_CLASSNAME); + if (schema.existsClass(OFunction.CLASS_NAME)) + schema.dropClass(OFunction.CLASS_NAME); + if (schema.existsClass("ORIDs")) + schema.dropClass("ORIDs"); + if (schema.existsClass(OClassTrigger.CLASSNAME)) + schema.dropClass(OClassTrigger.CLASSNAME); + schema.save(); + + database.dropCluster(OStorage.CLUSTER_DEFAULT_NAME, true); + + database.getStorage().setDefaultClusterId(database.addCluster(OStorage.CLUSTER_DEFAULT_NAME)); + + // Starting from v4 schema has been moved to internal cluster. + // Create a stub at #2:0 to prevent cluster position shifting. + new ODocument().save(OStorage.CLUSTER_DEFAULT_NAME); + + database.getMetadata().getSecurity().create(); + } + + private void importInfo() throws IOException, ParseException { + listener.onMessage("\nImporting database info..."); + + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + while (jsonReader.lastChar() != '}') { + final String fieldName = jsonReader.readString(OJSONReader.FIELD_ASSIGNMENT); + if (fieldName.equals("exporter-version")) + exporterVersion = jsonReader.readInteger(OJSONReader.NEXT_IN_OBJECT); + else if (fieldName.equals("schemaRecordId")) + schemaRecordId = new ORecordId(jsonReader.readString(OJSONReader.NEXT_IN_OBJECT)); + else if (fieldName.equals("indexMgrRecordId")) + indexMgrRecordId = new ORecordId(jsonReader.readString(OJSONReader.NEXT_IN_OBJECT)); + else + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + + if (schemaRecordId == null) + schemaRecordId = new ORecordId(database.getStorage().getConfiguration().schemaRecordId); + + if (indexMgrRecordId == null) + indexMgrRecordId = new ORecordId(database.getStorage().getConfiguration().indexMgrRecordId); + + listener.onMessage("OK"); + } + + private void removeDefaultNonSecurityClasses() { + listener.onMessage("\nNon merge mode (-merge=false): removing all default non security classes"); + + OSchema schema = database.getMetadata().getSchema(); + Collection classes = schema.getClasses(); + OClass orole = schema.getClass(ORole.CLASS_NAME); + OClass ouser = schema.getClass(OUser.CLASS_NAME); + OClass oidentity = schema.getClass(OIdentity.CLASS_NAME); + final Map classesToDrop = new HashMap(); + final Set indexes = new HashSet(); + for (OClass dbClass : classes) { + String className = dbClass.getName(); + + if (!dbClass.isSuperClassOf(orole) && !dbClass.isSuperClassOf(ouser) && !dbClass.isSuperClassOf(oidentity)) { + classesToDrop.put(className, dbClass); + for (OIndex index : dbClass.getIndexes()) { + indexes.add(index.getName()); + } + } + } + + final OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + for (String indexName : indexes) { + indexManager.dropIndex(indexName); + } + + int removedClasses = 0; + while (!classesToDrop.isEmpty()) { + final AbstractList classesReadyToDrop = new ArrayList(); + for (String className : classesToDrop.keySet()) { + boolean isSuperClass = false; + for (OClass dbClass : classesToDrop.values()) { + List parentClasses = dbClass.getSuperClasses(); + if (parentClasses != null) { + for (OClass parentClass : parentClasses) { + if (className.equalsIgnoreCase(parentClass.getName())) { + isSuperClass = true; + break; + } + } + } + } + if (!isSuperClass) { + classesReadyToDrop.add(className); + } + } + for (String className : classesReadyToDrop) { + schema.dropClass(className); + classesToDrop.remove(className); + removedClasses++; + listener.onMessage("\n- Class " + className + " was removed."); + } + } + + schema.save(); + schema.reload(); + + listener.onMessage("\nRemoved " + removedClasses + " classes."); + } + + private void importManualIndexes() throws IOException, ParseException { + listener.onMessage("\nImporting manual index entries..."); + + ODocument doc = new ODocument(); + + OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + // FORCE RELOADING + indexManager.reload(); + + int n = 0; + do { + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + + jsonReader.readString(OJSONReader.FIELD_ASSIGNMENT); + final String indexName = jsonReader.readString(OJSONReader.NEXT_IN_ARRAY); + + if (indexName == null || indexName.length() == 0) + return; + + listener.onMessage("\n- Index '" + indexName + "'..."); + + final OIndex index = database.getMetadata().getIndexManager().getIndex(indexName); + + long tot = 0; + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + do { + final String value = jsonReader.readString(OJSONReader.NEXT_IN_ARRAY).trim(); + + if (!value.isEmpty() && !indexName.equalsIgnoreCase(EXPORT_IMPORT_MAP_NAME)) { + doc = (ODocument) ORecordSerializerJSON.INSTANCE.fromString(value, doc, null); + doc.setLazyLoad(false); + + final OIdentifiable oldRid = doc.field("rid"); + final OIdentifiable newRid; + if (!doc.field("binary")) { + if (exportImportHashTable != null) + newRid = exportImportHashTable.get(oldRid); + else + newRid = oldRid; + + index.put(doc.field("key"), newRid != null ? newRid.getIdentity() : oldRid.getIdentity()); + } else { + ORuntimeKeyIndexDefinition runtimeKeyIndexDefinition = (ORuntimeKeyIndexDefinition) index.getDefinition(); + OBinarySerializer binarySerializer = runtimeKeyIndexDefinition.getSerializer(); + + if (exportImportHashTable != null) + newRid = exportImportHashTable.get(doc.field("rid")).getIdentity(); + else + newRid = doc.field("rid"); + + index.put(binarySerializer.deserialize(doc.field("key"), 0), newRid != null ? newRid : oldRid); + } + tot++; + } + } while (jsonReader.lastChar() == ','); + + if (index != null) { + listener.onMessage("OK (" + tot + " entries)"); + n++; + } else + listener.onMessage("ERR, the index wasn't found in configuration"); + + jsonReader.readNext(OJSONReader.END_OBJECT); + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + + } while (jsonReader.lastChar() == ','); + + listener.onMessage("\nDone. Imported " + String.format("%,d", n) + " indexes."); + + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } + + private void importSchema(boolean clustersImported) throws IOException, ParseException { + if (!clustersImported) { + removeDefaultClusters(); + } + + listener.onMessage("\nImporting database schema..."); + + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + @SuppressWarnings("unused") + int schemaVersion = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"version\"") + .readNumber(OJSONReader.ANY_NUMBER, true); + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + // This can be removed after the M1 expires + if (jsonReader.getValue().equals("\"globalProperties\"")) { + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + do { + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"name\""); + String name = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"global-id\""); + String id = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"type\""); + String type = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + // getDatabase().getMetadata().getSchema().createGlobalProperty(name, OType.valueOf(type), Integer.valueOf(id)); + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + } while (jsonReader.lastChar() == ','); + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + } + + if (jsonReader.getValue().equals("\"blob-clusters\"")) { + String blobClusterIds = jsonReader.readString(OJSONReader.END_COLLECTION, true).trim(); + blobClusterIds = blobClusterIds.substring(1, blobClusterIds.length() - 1); + + if (!"".equals(blobClusterIds)) { + // READ BLOB CLUSTER IDS + for (String i : OStringSerializerHelper.split(blobClusterIds, OStringSerializerHelper.RECORD_SEPARATOR)) { + Integer cluster = Integer.parseInt(i); + if (!database.getBlobClusterIds().contains(cluster)) { + String name = database.getClusterNameById(cluster); + database.addBlobCluster(name); + } + } + } + + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + } + + jsonReader.checkContent("\"classes\"").readNext(OJSONReader.BEGIN_COLLECTION); + + long classImported = 0; + + try { + do { + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + + String className = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"name\"") + .readString(OJSONReader.COMMA_SEPARATOR); + + String next = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).getValue(); + + if (next.equals("\"id\"")) { + // @COMPATIBILITY 1.0rc4 IGNORE THE ID + next = jsonReader.readString(OJSONReader.COMMA_SEPARATOR); + next = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).getValue(); + } + + final int classDefClusterId; + if (jsonReader.isContent("\"default-cluster-id\"")) { + next = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + classDefClusterId = Integer.parseInt(next); + } else + classDefClusterId = database.getDefaultClusterId(); + + String classClusterIds = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"cluster-ids\"") + .readString(OJSONReader.END_COLLECTION, true).trim(); + + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + + if (className.contains(".")) { + // MIGRATE OLD NAME WITH . TO _ + final String newClassName = className.replace('.', '_'); + convertedClassNames.put(className, newClassName); + + listener.onMessage("\nWARNING: class '" + className + "' has been renamed in '" + newClassName + "'\n"); + + className = newClassName; + } + + OClassImpl cls = (OClassImpl) database.getMetadata().getSchema().getClass(className); + + if (cls != null) { + if (cls.getDefaultClusterId() != classDefClusterId) + cls.setDefaultClusterId(classDefClusterId); + } else if (clustersImported) { + cls = (OClassImpl) database.getMetadata().getSchema().createClass(className, new int[] { classDefClusterId }); + } else if (className.equalsIgnoreCase("ORestricted")) { + cls = (OClassImpl) database.getMetadata().getSchema().createAbstractClass(className); + } else { + cls = (OClassImpl) database.getMetadata().getSchema().createClass(className); + } + + if (classClusterIds != null && clustersImported) { + // REMOVE BRACES + classClusterIds = classClusterIds.substring(1, classClusterIds.length() - 1); + + // ASSIGN OTHER CLUSTER IDS + for (int i : OStringSerializerHelper.splitIntArray(classClusterIds)) { + if (i != -1) + cls.addClusterId(i); + } + } + + String value; + while (jsonReader.lastChar() == ',') { + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + value = jsonReader.getValue(); + + if (value.equals("\"strictMode\"")) { + cls.setStrictMode(jsonReader.readBoolean(OJSONReader.NEXT_IN_OBJECT)); + } else if (value.equals("\"abstract\"")) { + cls.setAbstract(jsonReader.readBoolean(OJSONReader.NEXT_IN_OBJECT)); + } else if (value.equals("\"oversize\"")) { + final String oversize = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + cls.setOverSize(Float.parseFloat(oversize)); + } else if (value.equals("\"strictMode\"")) { + final String strictMode = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + cls.setStrictMode(Boolean.parseBoolean(strictMode)); + } else if (value.equals("\"short-name\"")) { + final String shortName = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + if (!cls.getName().equalsIgnoreCase(shortName)) + cls.setShortName(shortName); + } else if (value.equals("\"super-class\"")) { + // @compatibility <2.1 SINGLE CLASS ONLY + final String classSuper = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + final List superClassNames = new ArrayList(); + superClassNames.add(classSuper); + superClasses.put(cls, superClassNames); + } else if (value.equals("\"super-classes\"")) { + // MULTIPLE CLASSES + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + final List superClassNames = new ArrayList(); + while (jsonReader.lastChar() != ']') { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + + final String clsName = jsonReader.getValue(); + + superClassNames.add(OIOUtils.getStringContent(clsName)); + } + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + + superClasses.put(cls, superClassNames); + } else if (value.equals("\"properties\"")) { + // GET PROPERTIES + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + while (jsonReader.lastChar() != ']') { + importProperty(cls); + + if (jsonReader.lastChar() == '}') + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + } + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } else if (value.equals("\"customFields\"")) { + Map customFields = importCustomFields(); + for (Entry entry : customFields.entrySet()) { + cls.setCustom(entry.getKey(), entry.getValue()); + } + } else if (value.equals("\"cluster-selection\"")) { + // @SINCE 1.7 + cls.setClusterSelection(jsonReader.readString(OJSONReader.NEXT_IN_OBJECT)); + } + } + + classImported++; + + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + } while (jsonReader.lastChar() == ','); + + // REBUILD ALL THE INHERITANCE + for (Map.Entry> entry : superClasses.entrySet()) + for (String s : entry.getValue()) { + OClass superClass = database.getMetadata().getSchema().getClass(s); + ; + if (!entry.getKey().getSuperClasses().contains(superClass)) + entry.getKey().addSuperClass(superClass); + } + + // SET ALL THE LINKED CLASSES + for (Map.Entry entry : linkedClasses.entrySet()) { + entry.getKey().setLinkedClass(database.getMetadata().getSchema().getClass(entry.getValue())); + } + + database.getMetadata().getSchema().save(); + + if (exporterVersion < 11) { + OClass role = database.getMetadata().getSchema().getClass("ORole"); + role.dropProperty("rules"); + } + + listener.onMessage("OK (" + classImported + " classes)"); + schemaImported = true; + jsonReader.readNext(OJSONReader.END_OBJECT); + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on importing schema", e); + listener.onMessage("ERROR (" + classImported + " entries): " + e); + } + } + + private void importProperty(final OClass iClass) throws IOException, ParseException { + jsonReader.readNext(OJSONReader.NEXT_OBJ_IN_ARRAY); + + if (jsonReader.lastChar() == ']') + return; + + final String propName = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"name\"") + .readString(OJSONReader.COMMA_SEPARATOR); + + String next = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).getValue(); + + if (next.equals("\"id\"")) { + // @COMPATIBILITY 1.0rc4 IGNORE THE ID + next = jsonReader.readString(OJSONReader.COMMA_SEPARATOR); + next = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).getValue(); + } + next = jsonReader.checkContent("\"type\"").readString(OJSONReader.NEXT_IN_OBJECT); + + final OType type = OType.valueOf(next); + + String attrib; + String value = null; + + String min = null; + String max = null; + String linkedClass = null; + OType linkedType = null; + boolean mandatory = false; + boolean readonly = false; + boolean notNull = false; + String collate = null; + String regexp = null; + String defaultValue = null; + + Map customFields = null; + + while (jsonReader.lastChar() == ',') { + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + + attrib = jsonReader.getValue(); + if (!attrib.equals("\"customFields\"")) + value = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT, false, OJSONReader.DEFAULT_JUMP, null, false); + + if (attrib.equals("\"min\"")) + min = value; + else if (attrib.equals("\"max\"")) + max = value; + else if (attrib.equals("\"linked-class\"")) + linkedClass = value; + else if (attrib.equals("\"mandatory\"")) + mandatory = Boolean.parseBoolean(value); + else if (attrib.equals("\"readonly\"")) + readonly = Boolean.parseBoolean(value); + else if (attrib.equals("\"not-null\"")) + notNull = Boolean.parseBoolean(value); + else if (attrib.equals("\"linked-type\"")) + linkedType = OType.valueOf(value); + else if (attrib.equals("\"collate\"")) + collate = value; + else if (attrib.equals("\"default-value\"")) + defaultValue = value; + else if (attrib.equals("\"customFields\"")) + customFields = importCustomFields(); + else if (attrib.equals("\"regexp\"")) + regexp = value; + } + + OPropertyImpl prop = (OPropertyImpl) iClass.getProperty(propName); + if (prop == null) { + // CREATE IT + prop = (OPropertyImpl) iClass.createProperty(propName, type, (OType) null, true); + } + prop.setMandatory(mandatory); + prop.setReadonly(readonly); + prop.setNotNull(notNull); + + if (min != null) + prop.setMin(min); + if (max != null) + prop.setMax(max); + if (linkedClass != null) + linkedClasses.put(prop, linkedClass); + if (linkedType != null) + prop.setLinkedType(linkedType); + if (collate != null) + prop.setCollate(collate); + if (regexp != null) + prop.setRegexp(regexp); + if (defaultValue != null) { + prop.setDefaultValue(value); + } + if (customFields != null) { + for (Map.Entry entry : customFields.entrySet()) { + prop.setCustom(entry.getKey(), entry.getValue()); + } + } + } + + private Map importCustomFields() throws ParseException, IOException { + Map result = new HashMap(); + + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + + while (jsonReader.lastChar() != '}') { + final String key = jsonReader.readString(OJSONReader.FIELD_ASSIGNMENT); + final String value = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + + result.put(key, value); + } + + jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + + return result; + } + + private long importClusters() throws ParseException, IOException { + listener.onMessage("\nImporting clusters..."); + + long total = 0; + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + boolean recreateManualIndex = false; + if (exporterVersion <= 4) { + removeDefaultClusters(); + recreateManualIndex = true; + } + + final Set indexesToRebuild = new HashSet(); + + @SuppressWarnings("unused") + ORecordId rid = null; + while (jsonReader.lastChar() != ']') { + jsonReader.readNext(OJSONReader.BEGIN_OBJECT); + + String name = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"name\"") + .readString(OJSONReader.COMMA_SEPARATOR); + + if (name.length() == 0) + name = null; + + name = OClassImpl.decodeClassName(name); + if (name != null) + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(name)) { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + continue; + } + } else if (excludeClusters != null) { + if (excludeClusters.contains(name)) { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + continue; + } + } + + int id; + if (exporterVersion < 9) { + id = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"id\"").readInteger(OJSONReader.COMMA_SEPARATOR); + String type = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"type\"") + .readString(OJSONReader.NEXT_IN_OBJECT); + } else + id = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"id\"").readInteger(OJSONReader.NEXT_IN_OBJECT); + + String type; + if (jsonReader.lastChar() == ',') + type = jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"type\"").readString(OJSONReader.NEXT_IN_OBJECT); + else + type = "PHYSICAL"; + + if (jsonReader.lastChar() == ',') { + rid = new ORecordId( + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT).checkContent("\"rid\"").readString(OJSONReader.NEXT_IN_OBJECT)); + } else + rid = null; + + listener.onMessage("\n- Creating cluster " + (name != null ? "'" + name + "'" : "NULL") + "..."); + + int clusterId = name != null ? database.getClusterIdByName(name) : -1; + if (clusterId == -1) { + // CREATE IT + if (!preserveClusterIDs) + clusterId = database.addCluster(name); + else { + clusterId = database.addCluster(name, id, null); + assert clusterId == id; + } + } + + if (clusterId != id) { + if (!preserveClusterIDs) { + if (database.countClusterElements(clusterId - 1) == 0) { + listener.onMessage("Found previous version: migrating old clusters..."); + database.dropCluster(name, true); + database.addCluster("temp_" + clusterId, null); + clusterId = database.addCluster(name); + } else + throw new OConfigurationException( + "Imported cluster '" + name + "' has id=" + clusterId + " different from the original: " + id + + ". To continue the import drop the cluster '" + database.getClusterNameById(clusterId - 1) + "' that has " + + database.countClusterElements(clusterId - 1) + " records"); + } else { + database.dropCluster(clusterId, false); + database.addCluster(name, id, null); + } + } + + if (name != null && !(name.equalsIgnoreCase(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME) || name + .equalsIgnoreCase(OMetadataDefault.CLUSTER_INTERNAL_NAME) || name + .equalsIgnoreCase(OMetadataDefault.CLUSTER_INDEX_NAME))) { + if (!merge) + database.command(new OCommandSQL("truncate cluster `" + name + "`")).execute(); + + for (OIndex existingIndex : database.getMetadata().getIndexManager().getIndexes()) { + if (existingIndex.getClusters().contains(name)) { + indexesToRebuild.add(existingIndex.getName()); + } + } + } + + listener.onMessage("OK, assigned id=" + clusterId); + + total++; + + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + } + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + + listener.onMessage("\nRebuilding indexes of truncated clusters ..."); + + for (final String indexName : indexesToRebuild) + database.getMetadata().getIndexManager().getIndex(indexName).rebuild(new OProgressListener() { + private long last = 0; + + @Override + public void onBegin(Object iTask, long iTotal, Object metadata) { + listener.onMessage("\n- Cluster content was updated: rebuilding index '" + indexName + "'..."); + } + + @Override + public boolean onProgress(Object iTask, long iCounter, float iPercent) { + final long now = System.currentTimeMillis(); + if (last == 0) + last = now; + else if (now - last > 1000) { + listener.onMessage(String.format("\nIndex '%s' is rebuilding (%.2f/100)", indexName, iPercent)); + last = now; + } + return true; + } + + @Override + public void onCompletition(Object iTask, boolean iSucceed) { + listener.onMessage(" Index " + indexName + " was successfully rebuilt."); + } + }); + + listener.onMessage("\nDone " + indexesToRebuild.size() + " indexes were rebuilt."); + + if (recreateManualIndex) { + database.addCluster(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME); + database.getMetadata().getIndexManager().create(); + + listener.onMessage("\nManual index cluster was recreated."); + } + + listener.onMessage("\nDone. Imported " + total + " clusters"); + + if (database.load(new ORecordId(database.getStorage().getConfiguration().indexMgrRecordId)) == null) { + ODocument indexDocument = new ODocument(); + indexDocument.save(OMetadataDefault.CLUSTER_INTERNAL_NAME); + + database.getStorage().getConfiguration().indexMgrRecordId = indexDocument.getIdentity().toString(); + database.getStorage().getConfiguration().update(); + } + + return total; + } + + private long importRecords() throws Exception { + long total = 0; + + database.getMetadata().getIndexManager().dropIndex(EXPORT_IMPORT_MAP_NAME); + OIndexFactory factory = OIndexes + .getFactory(OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.toString(), OHashIndexFactory.HASH_INDEX_ALGORITHM); + + exportImportHashTable = (OIndex) database.getMetadata().getIndexManager() + .createIndex(EXPORT_IMPORT_MAP_NAME, OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.toString(), + new OSimpleKeyIndexDefinition(factory.getLastVersion(), OType.LINK), null, null, null); + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + long totalRecords = 0; + + listener.onMessage("\n\nImporting records..."); + + ORID rid; + ORID lastRid = new ORecordId(); + final long begin = System.currentTimeMillis(); + long lastLapRecords = 0; + long last = begin; + Set involvedClusters = new HashSet(); + + while (jsonReader.lastChar() != ']') { + rid = importRecord(); + + if (rid != null) { + ++lastLapRecords; + ++totalRecords; + + if (rid.getClusterId() != lastRid.getClusterId() || involvedClusters.isEmpty()) + involvedClusters.add(database.getClusterNameById(rid.getClusterId())); + + final long now = System.currentTimeMillis(); + if (now - last > IMPORT_RECORD_DUMP_LAP_EVERY_MS) { + final List sortedClusters = new ArrayList(involvedClusters); + Collections.sort(sortedClusters); + + listener.onMessage(String + .format("\n- Imported %,d records into clusters: %s. Total records imported so far: %,d (%,.2f/sec)", lastLapRecords, + sortedClusters, totalRecords, (float) lastLapRecords * 1000 / (float) IMPORT_RECORD_DUMP_LAP_EVERY_MS)); + + // RESET LAP COUNTERS + last = now; + lastLapRecords = 0; + involvedClusters.clear(); + } + lastRid = rid; + } + + record = null; + } + + final Set brokenRids = new HashSet(); + + if (exporterVersion >= 12) { + listener.onMessage("Reading of set of RIDs of records which were detected as broken during database export\n"); + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + while (true) { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + + final ORecordId recordId = new ORecordId(jsonReader.getValue()); + brokenRids.add(recordId); + + if (jsonReader.lastChar() == ']') + break; + } + } + if (migrateLinks) { + if (exporterVersion >= 12) + listener.onMessage( + brokenRids.size() + " were detected as broken during database export, links on those records will be removed from" + + " result database"); + migrateLinksInImportedDocuments(brokenRids); + } + + listener.onMessage(String.format("\n\nDone. Imported %,d records in %,.2f secs\n", totalRecords, + ((float) (System.currentTimeMillis() - begin)) / 1000)); + + jsonReader.readNext(OJSONReader.COMMA_SEPARATOR); + + return total; + } + + private ORID importRecord() throws Exception { + String value = jsonReader.readString(OJSONReader.END_OBJECT, true); + + // JUMP EMPTY RECORDS + while (!value.isEmpty() && value.charAt(0) != '{') { + value = value.substring(1); + } + + record = null; + try { + + try { + record = ORecordSerializerJSON.INSTANCE.fromString(value, record, null); + } catch (OSerializationException e) { + if (e.getCause() instanceof OSchemaException) { + // EXTRACT CLASS NAME If ANY + final int pos = value.indexOf("\"@class\":\""); + if (pos > -1) { + final int end = value.indexOf("\"", pos + "\"@class\":\"".length() + 1); + final String value1 = value.substring(0, pos + "\"@class\":\"".length()); + final String clsName = value.substring(pos + "\"@class\":\"".length(), end); + final String value2 = value.substring(end); + + final String newClassName = convertedClassNames.get(clsName); + + value = value1 + newClassName + value2; + // OVERWRITE CLASS NAME WITH NEW NAME + record = ORecordSerializerJSON.INSTANCE.fromString(value, record, null); + } + } else + throw OException.wrapException(new ODatabaseImportException("Error on importing record"), e); + } + + // Incorrect record format , skip this record + if (record == null || record.getIdentity() == null) { + OLogManager.instance().warn(this, "Broken record was detected and will be skipped"); + return null; + } + + if (schemaImported && record.getIdentity().equals(schemaRecordId)) { + // JUMP THE SCHEMA + return null; + } + + // CHECK IF THE CLUSTER IS INCLUDED + if (includeClusters != null) { + if (!includeClusters.contains(database.getClusterNameById(record.getIdentity().getClusterId()))) { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + return null; + } + } else if (excludeClusters != null) { + if (excludeClusters.contains(database.getClusterNameById(record.getIdentity().getClusterId()))) + return null; + } + + if (record.getIdentity().getClusterId() == 0 && record.getIdentity().getClusterPosition() == 1) + // JUMP INTERNAL RECORDS + return null; + + if (exporterVersion >= 3) { + int oridsId = database.getClusterIdByName("ORIDs"); + int indexId = database.getClusterIdByName(OMetadataDefault.CLUSTER_INDEX_NAME); + + if (record.getIdentity().getClusterId() == indexId || record.getIdentity().getClusterId() == oridsId) + // JUMP INDEX RECORDS + return null; + } + + final int manualIndexCluster = database.getClusterIdByName(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME); + final int internalCluster = database.getClusterIdByName(OMetadataDefault.CLUSTER_INTERNAL_NAME); + final int indexCluster = database.getClusterIdByName(OMetadataDefault.CLUSTER_INDEX_NAME); + + if (exporterVersion >= 4) { + if (record.getIdentity().getClusterId() == manualIndexCluster) + // JUMP INDEX RECORDS + return null; + } + + if (record.getIdentity().equals(indexMgrRecordId)) + return null; + + final ORID rid = record.getIdentity(); + + final int clusterId = rid.getClusterId(); + + if ((clusterId != manualIndexCluster && clusterId != internalCluster && clusterId != indexCluster)) { + ORecordInternal.setVersion(record, 0); + record.setDirty(); + ORecordInternal.setIdentity(record, new ORecordId()); + + if (!preserveRids && record instanceof ODocument && ODocumentInternal.getImmutableSchemaClass(((ODocument) record)) != null) + record.save(); + else + record.save(database.getClusterNameById(clusterId)); + + if (!rid.equals(record.getIdentity())) + // SAVE IT ONLY IF DIFFERENT + exportImportHashTable.put(rid, record.getIdentity()); + } + + } catch (Exception t) { + if (record != null) + OLogManager.instance().error(this, + "Error importing record " + record.getIdentity() + ". Source line " + jsonReader.getLineNumber() + ", column " + + jsonReader.getColumnNumber()); + else + OLogManager.instance().error(this, + "Error importing record. Source line " + jsonReader.getLineNumber() + ", column " + jsonReader.getColumnNumber()); + + if (!(t instanceof ODatabaseException)) { + throw t; + } + } finally { + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + } + + return record.getIdentity(); + } + + private void importIndexes() throws IOException, ParseException { + listener.onMessage("\n\nImporting indexes ..."); + + OIndexManagerProxy indexManager = database.getMetadata().getIndexManager(); + indexManager.reload(); + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + int n = 0; + while (jsonReader.lastChar() != ']') { + jsonReader.readNext(OJSONReader.NEXT_OBJ_IN_ARRAY); + if (jsonReader.lastChar() == ']') { + break; + } + + String blueprintsIndexClass = null; + String indexName = null; + String indexType = null; + String indexAlgorithm = null; + Set clustersToIndex = new HashSet(); + OIndexDefinition indexDefinition = null; + ODocument metadata = null; + Map engineProperties = null; + + while (jsonReader.lastChar() != '}') { + final String fieldName = jsonReader.readString(OJSONReader.FIELD_ASSIGNMENT); + if (fieldName.equals("name")) + indexName = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + else if (fieldName.equals("type")) + indexType = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + else if (fieldName.equals("algorithm")) + indexAlgorithm = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + else if (fieldName.equals("clustersToIndex")) + clustersToIndex = importClustersToIndex(); + else if (fieldName.equals("definition")) { + indexDefinition = importIndexDefinition(); + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } else if (fieldName.equals("metadata")) { + final String jsonMetadata = jsonReader.readString(OJSONReader.END_OBJECT, true); + metadata = new ODocument().fromJSON(jsonMetadata); + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } else if (fieldName.equals("engineProperties")) { + final String jsonEngineProperties = jsonReader.readString(OJSONReader.END_OBJECT, true); + Map map = new ODocument().fromJSON(jsonEngineProperties).toMap(); + if (map != null) { + engineProperties = new HashMap(map.size()); + for (Entry entry : map.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + } + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } else if (fieldName.equals("blueprintsIndexClass")) + blueprintsIndexClass = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + } + + if (indexName == null) + throw new IllegalArgumentException("Index name is missing"); + + jsonReader.readNext(OJSONReader.NEXT_IN_ARRAY); + + // drop automatically created indexes + if (!indexName.equalsIgnoreCase(EXPORT_IMPORT_MAP_NAME)) { + listener.onMessage("\n- Index '" + indexName + "'..."); + + indexManager.dropIndex(indexName); + indexesToRebuild.remove(indexName.toLowerCase(Locale.ENGLISH)); + List clusterIds = new ArrayList(); + + for (final String clusterName : clustersToIndex) { + int id = database.getClusterIdByName(clusterName); + if (id != -1) + clusterIds.add(id); + else + listener.onMessage( + String.format("found not existent cluster '%s' in index '%s' configuration, skipping", clusterName, indexName)); + } + int[] clusterIdsToIndex = new int[clusterIds.size()]; + + int i = 0; + for (Integer clusterId : clusterIds) { + clusterIdsToIndex[i] = clusterId; + i++; + } + + final OIndex index = indexManager + .createIndex(indexName, indexType, indexDefinition, clusterIdsToIndex, null, metadata, indexAlgorithm); + + if (blueprintsIndexClass != null) { + ODocument configuration = index.getConfiguration(); + configuration.field("blueprintsIndexClass", blueprintsIndexClass); + indexManager.save(); + } + + n++; + listener.onMessage("OK"); + + } + } + + listener.onMessage("\nDone. Created " + n + " indexes."); + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + } + + private Set importClustersToIndex() throws IOException, ParseException { + final Set clustersToIndex = new HashSet(); + + jsonReader.readNext(OJSONReader.BEGIN_COLLECTION); + + while (jsonReader.lastChar() != ']') { + final String clusterToIndex = jsonReader.readString(OJSONReader.NEXT_IN_ARRAY); + clustersToIndex.add(clusterToIndex); + } + + jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + return clustersToIndex; + } + + private OIndexDefinition importIndexDefinition() throws IOException, ParseException { + jsonReader.readString(OJSONReader.BEGIN_OBJECT); + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + + final String className = jsonReader.readString(OJSONReader.NEXT_IN_OBJECT); + + jsonReader.readNext(OJSONReader.FIELD_ASSIGNMENT); + + final String value = jsonReader.readString(OJSONReader.END_OBJECT, true); + + final OIndexDefinition indexDefinition; + final ODocument indexDefinitionDoc = (ODocument) ORecordSerializerJSON.INSTANCE.fromString(value, null, null); + try { + final Class indexDefClass = Class.forName(className); + indexDefinition = (OIndexDefinition) indexDefClass.getDeclaredConstructor().newInstance(); + indexDefinition.fromStream(indexDefinitionDoc); + } catch (final ClassNotFoundException e) { + throw new IOException("Error during deserialization of index definition", e); + } catch (final NoSuchMethodException e) { + throw new IOException("Error during deserialization of index definition", e); + } catch (final InvocationTargetException e) { + throw new IOException("Error during deserialization of index definition", e); + } catch (final InstantiationException e) { + throw new IOException("Error during deserialization of index definition", e); + } catch (final IllegalAccessException e) { + throw new IOException("Error during deserialization of index definition", e); + } + + jsonReader.readNext(OJSONReader.NEXT_IN_OBJECT); + + return indexDefinition; + } + + private void migrateLinksInImportedDocuments(Set brokenRids) throws IOException { + listener.onMessage("\n\nStarted migration of links (-migrateLinks=true). Links are going to be updated according to new RIDs:"); + + final long begin = System.currentTimeMillis(); + long last = begin; + long documentsLastLap = 0; + + long totalDocuments = 0; + Collection clusterNames = database.getClusterNames(); + for (String clusterName : clusterNames) { + if (OMetadataDefault.CLUSTER_INDEX_NAME.equals(clusterName) || OMetadataDefault.CLUSTER_INTERNAL_NAME.equals(clusterName) + || OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME.equals(clusterName)) + continue; + + long documents = 0; + String prefix = ""; + + listener.onMessage("\n- Cluster " + clusterName + "..."); + + final int clusterId = database.getClusterIdByName(clusterName); + final long clusterRecords = database.countClusterElements(clusterId); + OStorage storage = database.getStorage(); + + OPhysicalPosition[] positions = storage.ceilingPhysicalPositions(clusterId, new OPhysicalPosition(0)); + while (positions.length > 0) { + for (OPhysicalPosition position : positions) { + ORecord record = database.load(new ORecordId(clusterId, position.clusterPosition)); + if (record instanceof ODocument) { + ODocument document = (ODocument) record; + rewriteLinksInDocument(document, brokenRids); + + documents++; + documentsLastLap++; + totalDocuments++; + + final long now = System.currentTimeMillis(); + if (now - last > IMPORT_RECORD_DUMP_LAP_EVERY_MS) { + listener.onMessage(String.format("\n--- Migrated %,d of %,d records (%,.2f/sec)", documents, clusterRecords, + (float) documentsLastLap * 1000 / (float) IMPORT_RECORD_DUMP_LAP_EVERY_MS)); + + // RESET LAP COUNTERS + last = now; + documentsLastLap = 0; + prefix = "\n---"; + } + } + } + + positions = storage.higherPhysicalPositions(clusterId, positions[positions.length - 1]); + } + + listener.onMessage(String.format("%s Completed migration of %,d records in current cluster", prefix, documents)); + } + + listener.onMessage(String.format("\nTotal links updated: %,d", totalDocuments)); + } + + private void rewriteLinksInDocument(ODocument document, Set brokenRids) { + rewriteLinksInDocument(document, exportImportHashTable, brokenRids); + document.save(); + } + + protected static void rewriteLinksInDocument(ODocument document, OIndex rewrite, Set brokenRids) { + LinkConverter.INSTANCE.setExportImportHashTable(rewrite); + LinkConverter.INSTANCE.setBrokenRids(brokenRids); + + final LinksRewriter rewriter = new LinksRewriter(); + final ODocumentFieldWalker documentFieldWalker = new ODocumentFieldWalker(); + documentFieldWalker.walkDocument(document, rewriter); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImportException.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImportException.java new file mode 100644 index 00000000000..6eb3b03a1cb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseImportException.java @@ -0,0 +1,31 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.db.tool; + +import com.orientechnologies.common.exception.OException; + +@SuppressWarnings("serial") +public class ODatabaseImportException extends OException { + + public ODatabaseImportException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseRepair.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseRepair.java new file mode 100755 index 00000000000..353f2cf22c3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseRepair.java @@ -0,0 +1,149 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Iterator; +import java.util.List; + +/** + * Repair database tool. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + * @since v2.2.0 + */ +public class ODatabaseRepair extends ODatabaseTool { + private boolean removeBrokenLinks = true; + + @Override + protected void parseSetting(final String option, final List items) { + if (option.equalsIgnoreCase("-excludeAll")) { + + removeBrokenLinks = false; + + } else if (option.equalsIgnoreCase("-removeBrokenLinks")) { + + removeBrokenLinks = Boolean.parseBoolean(items.get(0)); + + } + } + + public void run() { + long errors = 0; + + if (removeBrokenLinks) + errors += removeBrokenLinks(); + + message("\nRepair database complete (" + errors + " errors)"); + } + + protected long removeBrokenLinks() { + long fixedLinks = 0l; + long modifiedDocuments = 0l; + long errors = 0l; + + message("\n- Removing broken links..."); + for (String clusterName : database.getClusterNames()) { + for (ORecord rec : database.browseCluster(clusterName)) { + try { + if (rec instanceof ODocument) { + boolean changed = false; + + final ODocument doc = (ODocument) rec; + for (String fieldName : doc.fieldNames()) { + final Object fieldValue = doc.rawField(fieldName); + + if (fieldValue instanceof OIdentifiable) { + if (fixLink(fieldValue)) { + doc.field(fieldName, (OIdentifiable) null); + fixedLinks++; + changed = true; + if (verbose) + message("\n--- reset link " + ((OIdentifiable) fieldValue).getIdentity() + " in field '" + fieldName + "' (rid=" + + doc.getIdentity() + ")"); + } + } else if (fieldValue instanceof Iterable) { + if (fieldValue instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) fieldValue).setAutoConvertToRecord(false); + + final Iterator it = ((Iterable) fieldValue).iterator(); + for (int i = 0; it.hasNext(); ++i) { + final Object v = it.next(); + if (fixLink(v)) { + it.remove(); + fixedLinks++; + changed = true; + if (verbose) + message("\n--- reset link " + ((OIdentifiable) v).getIdentity() + " as item " + i + + " in collection of field '" + fieldName + "' (rid=" + doc.getIdentity() + ")"); + } + } + } + } + + if (changed) { + modifiedDocuments++; + doc.save(); + + if (verbose) + message("\n-- updated document " + doc.getIdentity()); + } + } + } catch (Exception e) { + errors++; + } + } + } + + message("\n-- Done! Fixed links: " + fixedLinks + ", modified documents: " + modifiedDocuments); + return errors; + } + + /** + * Checks if the link must be fixed. + * + * @param fieldValue + * Field containing the OIdentifiable (RID or Record) + * @return true to fix it, otherwise false + */ + protected boolean fixLink(final Object fieldValue) { + if (fieldValue instanceof OIdentifiable) { + final ORID id = ((OIdentifiable) fieldValue).getIdentity(); + + if (id.getClusterId() == 0 && id.getClusterPosition() == 0) + return true; + + if (id.isValid()) + if (id.isPersistent()) { + final ORecord connected = ((OIdentifiable) fieldValue).getRecord(); + if (connected == null) + return true; + } else + return true; + } + return false; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseTool.java b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseTool.java new file mode 100644 index 00000000000..717e1b46094 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/db/tool/ODatabaseTool.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.db.tool; + +import java.util.Collections; +import java.util.List; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandOutputListener; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +/** + * Base class for tools related to databases. + * + * @author Luca Garulli + */ +public abstract class ODatabaseTool implements Runnable { + protected OCommandOutputListener output; + protected ODatabaseDocument database; + protected boolean verbose = false; + + protected abstract void parseSetting(final String option, final List items); + + protected void message(final String iMessage, final Object... iArgs) { + if (output != null) + output.onMessage(String.format(iMessage, iArgs)); + } + + public ODatabaseTool setOptions(final String iOptions) { + if (iOptions != null) { + final List options = OStringSerializerHelper.smartSplit(iOptions, ' '); + for (String o : options) { + final int sep = o.indexOf('='); + if (sep == -1) { + parseSetting(o, Collections.EMPTY_LIST); + } else { + final String option = o.substring(0, sep); + final String value = OIOUtils.getStringContent(o.substring(sep + 1)); + final List items = OStringSerializerHelper.smartSplit(value, ' '); + parseSetting(option, items); + } + } + } + return this; + } + + public ODatabaseTool setOutputListener(final OCommandOutputListener iListener) { + output = iListener; + return this; + } + + public ODatabaseTool setDatabase(final ODatabaseDocument iDatabase) { + database = iDatabase; + return this; + } + + public ODatabaseTool setVerbose(final boolean verbose) { + this.verbose = verbose; + return this; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/dictionary/ODictionary.java b/core/src/main/java/com/orientechnologies/orient/core/dictionary/ODictionary.java new file mode 100644 index 00000000000..4e6525ff940 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/dictionary/ODictionary.java @@ -0,0 +1,74 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.dictionary; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.record.impl.ODocument; + +@SuppressWarnings("unchecked") +public class ODictionary { + private OIndex index; + + public ODictionary(final OIndex iIndex) { + index = iIndex; + } + + public RET get(final String iKey) { + final OIdentifiable value = index.get(iKey); + if (value == null) + return null; + + return (RET) value.getRecord(); + } + + public RET get(final String iKey, final String iFetchPlan) { + final OIdentifiable value = index.get(iKey); + if (value == null) + return null; + + if (value instanceof ORID) + return (RET) ODatabaseRecordThreadLocal.INSTANCE.get().load(((ORID) value), iFetchPlan); + + return (RET) ((ODocument) value).load(iFetchPlan); + } + + public void put(final String iKey, final Object iValue) { + index.put(iKey, (OIdentifiable) iValue); + } + + public boolean containsKey(final String iKey) { + return index.contains(iKey); + } + + public boolean remove(final String iKey) { + return index.remove(iKey); + } + + public long size() { + return index.getSize(); + } + + public OIndex getIndex() { + return index; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryption.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryption.java new file mode 100755 index 00000000000..e2f8607a015 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryption.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.encryption; + +/** + * Storage encryption interface. Additional encryption implementations can be plugged via register() method. There are + * 2 versions:
      + *
        + *
      • OEncryptionFactory.INSTANCE.register() for stateful implementations, a new instance will be created for + * each storage/li> + *
      • OEncryptionFactory.INSTANCE.register() for stateless implementations, the same instance will be shared + * across all the storages./li> + *
      + * + * @author Luca Garulli + */ +public interface OEncryption { + byte[] encrypt(byte[] content); + + byte[] decrypt(byte[] content); + + byte[] encrypt(byte[] content, final int offset, final int length); + + byte[] decrypt(byte[] content, final int offset, final int length); + + String name(); + + OEncryption configure(String iOptions); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryptionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryptionFactory.java new file mode 100755 index 00000000000..393ee9dfd06 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/OEncryptionFactory.java @@ -0,0 +1,128 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.encryption; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.encryption.impl.OAESEncryption; +import com.orientechnologies.orient.core.encryption.impl.ODESEncryption; +import com.orientechnologies.orient.core.encryption.impl.ONothingEncryption; +import com.orientechnologies.orient.core.exception.OSecurityException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Factory of encryption algorithms. + * + * @author Luca Garulli + */ +public class OEncryptionFactory { + public static final OEncryptionFactory INSTANCE = new OEncryptionFactory(); + + private final Map instances = new HashMap(); + private final Map> classes = new HashMap>(); + + /** + * Install default encryption algorithms. + */ + public OEncryptionFactory() { + register(ONothingEncryption.class); + register(ODESEncryption.class); + register(OAESEncryption.class); + } + + public OEncryption getEncryption(final String name, final String iOptions) { + OEncryption encryption = instances.get(name); + if (encryption == null) { + + final Class encryptionClass; + + if (name == null) + encryptionClass = ONothingEncryption.class; + else + encryptionClass = classes.get(name); + + if (encryptionClass != null) { + try { + encryption = encryptionClass.newInstance(); + encryption.configure(iOptions); + + } catch (Exception e) { + throw OException.wrapException(new OSecurityException("Cannot instantiate encryption algorithm '" + name + "'"), e); + } + } else + throw new OSecurityException("Encryption with name '" + name + "' is absent"); + } + return encryption; + } + + /** + * Registers a stateful implementations, a new instance will be created for each storage. + * + * @param iEncryption + * Encryption instance + */ + public void register(final OEncryption iEncryption) { + try { + final String name = iEncryption.name(); + + if (instances.containsKey(name)) + throw new IllegalArgumentException("Encryption with name '" + name + "' was already registered"); + + if (classes.containsKey(name)) + throw new IllegalArgumentException("Encryption with name '" + name + "' was already registered"); + + instances.put(name, iEncryption); + } catch (Exception e) { + OLogManager.instance().error(this, "Cannot register storage encryption algorithm '%s'", e, iEncryption); + } + } + + /** + * Registers a stateless implementations, the same instance will be shared on all the storages. + * + * @param iEncryption + * Encryption class + */ + public void register(final Class iEncryption) { + try { + final OEncryption tempInstance = iEncryption.newInstance(); + + final String name = tempInstance.name(); + + if (instances.containsKey(name)) + throw new IllegalArgumentException("Encryption with name '" + name + "' was already registered"); + + if (classes.containsKey(tempInstance.name())) + throw new IllegalArgumentException("Encryption with name '" + name + "' was already registered"); + + classes.put(name, iEncryption); + } catch (Exception e) { + OLogManager.instance().error(this, "Cannot register storage encryption algorithm '%s'", e, iEncryption); + } + } + + public Set getInstances() { + return instances.keySet(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAESEncryption.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAESEncryption.java new file mode 100755 index 00000000000..2a71a3fa3fd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAESEncryption.java @@ -0,0 +1,82 @@ +package com.orientechnologies.orient.core.encryption.impl; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.encryption.OEncryption; +import com.orientechnologies.orient.core.exception.OInvalidStorageEncryptionKeyException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/*** + * Stateful compression implementation that encrypt the content using AES + * (https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html). Issue + * https://github.com/orientechnologies/orientdb/issues/89. + * + * @author giastfader + * @author Luca Garulli + * + */ +public class OAESEncryption extends OAbstractEncryption { + // @see https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider + private final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; // we use ECB because we cannot store the + private final String ALGORITHM_NAME = "AES"; + + private SecretKeySpec theKey; + private Cipher cipher; + + private boolean initialized = false; + + public static final String NAME = "aes"; + + @Override + public String name() { + return NAME; + } + + public OAESEncryption() { + } + + public OEncryption configure(final String iOptions) { + initialized = false; + + if (iOptions == null) + throw new OSecurityException( + "AES encryption has been selected, but no key was found. Please configure it by passing the key as property at database create/open. The property key is: '" + + OGlobalConfiguration.STORAGE_ENCRYPTION_KEY.getKey() + "'"); + + try { + final byte[] key = OBase64Utils.decode(iOptions); + + theKey = new SecretKeySpec(key, ALGORITHM_NAME); // AES + cipher = Cipher.getInstance(TRANSFORMATION); + + } catch (Exception e) { + throw OException.wrapException(new OInvalidStorageEncryptionKeyException( + "Cannot initialize AES encryption with current key. Assure the key is a BASE64 - 128 oe 256 bits long"), e); + + } + + this.initialized = true; + + return this; + } + + public byte[] encryptOrDecrypt(final int mode, final byte[] input, final int offset, final int length) throws Exception { + if (!initialized) + throw new OSecurityException("AES encryption algorithm is not available"); + + cipher.init(mode, theKey); + + final byte[] content; + if (offset == 0 && length == input.length) { + content = input; + } else { + content = new byte[length]; + System.arraycopy(input, offset, content, 0, length); + } + return cipher.doFinal(content); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAbstractEncryption.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAbstractEncryption.java new file mode 100755 index 00000000000..64177299a14 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/OAbstractEncryption.java @@ -0,0 +1,56 @@ +package com.orientechnologies.orient.core.encryption.impl; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.encryption.OEncryption; +import com.orientechnologies.orient.core.exception.OInvalidStorageEncryptionKeyException; + +import javax.crypto.Cipher; + +/*** + * (https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html). Issue + * https://github.com/orientechnologies/orientdb/issues/89. + * + * @author giastfader + * + */ +public abstract class OAbstractEncryption implements OEncryption { + /*** + * + * @param mode + * it can be Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE + * @param input + * @param offset + * @param length + * @return + * @throws Throwable + */ + public abstract byte[] encryptOrDecrypt(int mode, byte[] input, int offset, int length) throws Exception; + + @Override + public byte[] encrypt(final byte[] content) { + return encrypt(content, 0, content.length); + } + + @Override + public byte[] decrypt(final byte[] content) { + return decrypt(content, 0, content.length); + } + + @Override + public byte[] encrypt(final byte[] content, final int offset, final int length) { + try { + return encryptOrDecrypt(Cipher.ENCRYPT_MODE, content, offset, length); + } catch (Exception e) { + throw OException.wrapException(new OInvalidStorageEncryptionKeyException("Cannot encrypt content"), e); + } + }; + + @Override + public byte[] decrypt(final byte[] content, final int offset, final int length) { + try { + return encryptOrDecrypt(Cipher.DECRYPT_MODE, content, offset, length); + } catch (Exception e) { + throw OException.wrapException(new OInvalidStorageEncryptionKeyException("Cannot decrypt content"), e); + } + }; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ODESEncryption.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ODESEncryption.java new file mode 100755 index 00000000000..43d1d8a39b6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ODESEncryption.java @@ -0,0 +1,85 @@ +package com.orientechnologies.orient.core.encryption.impl; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.encryption.OEncryption; +import com.orientechnologies.orient.core.exception.OInvalidStorageEncryptionKeyException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + +/*** + * Stateful compression implementation that encrypt the content using DES algorithm + * (https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html). Issue + * https://github.com/orientechnologies/orientdb/issues/89. + * + * @author giastfader + * + */ +public class ODESEncryption extends OAbstractEncryption { + // @see https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider + private final String TRANSFORMATION = "DES/ECB/PKCS5Padding"; // //we use ECB because we cannot + private final String ALGORITHM_NAME = "DES"; + + private SecretKey theKey; + private Cipher cipher; + + private boolean initialized = false; + + public static final String NAME = "des"; + + @Override + public String name() { + return NAME; + } + + public ODESEncryption() { + } + + public OEncryption configure(final String iOptions) { + initialized = false; + + if (iOptions == null) + throw new OSecurityException( + "DES encryption has been selected, but no key was found. Please configure it by passing the key as property at database create/open. The property key is: '" + + OGlobalConfiguration.STORAGE_ENCRYPTION_KEY.getKey() + "'"); + + try { + final byte[] key = OBase64Utils.decode(iOptions); + + final DESKeySpec desKeySpec = new DESKeySpec(key); + final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM_NAME); + + theKey = keyFactory.generateSecret(desKeySpec); + cipher = Cipher.getInstance(TRANSFORMATION); + + } catch (Exception e) { + throw OException.wrapException(new OInvalidStorageEncryptionKeyException( + "Cannot initialize DES encryption with current key. Assure the key is a BASE64 - 64 bits long"), e); + } + + this.initialized = true; + + return this; + } + + public byte[] encryptOrDecrypt(final int mode, final byte[] input, final int offset, final int length) throws Exception { + if (!initialized) + throw new OSecurityException("DES encryption algorithm is not available"); + + cipher.init(mode, theKey); + + final byte[] content; + if (offset == 0 && length == input.length) { + content = input; + } else { + content = new byte[length]; + System.arraycopy(input, offset, content, 0, length); + } + return cipher.doFinal(content); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ONothingEncryption.java b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ONothingEncryption.java new file mode 100755 index 00000000000..9d54370d371 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/encryption/impl/ONothingEncryption.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.encryption.impl; + +import com.orientechnologies.orient.core.encryption.OEncryption; + +/** + * No encryption. + * + * @author Luca Garulli + */ +public class ONothingEncryption implements OEncryption { + public static final String NAME = "nothing"; + + public static final ONothingEncryption INSTANCE = new ONothingEncryption(); + + @Override + public byte[] encrypt(final byte[] content) { + return content; + } + + @Override + public byte[] decrypt(final byte[] content) { + return content; + } + + @Override + public byte[] encrypt(final byte[] content, final int offset, final int length) { + if (offset == 0 && length == content.length) + return content; + + byte[] result = new byte[length]; + System.arraycopy(content, offset, result, 0, length); + + return result; + } + + @Override + public byte[] decrypt(final byte[] content, final int offset, final int length) { + if (offset == 0 && length == content.length) + return content; + + byte[] result = new byte[length]; + System.arraycopy(content, offset, result, 0, length); + + return result; + } + + @Override + public String name() { + return NAME; + } + + @Override + public OEncryption configure(String iOptions) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/engine/OEngine.java b/core/src/main/java/com/orientechnologies/orient/core/engine/OEngine.java new file mode 100644 index 00000000000..6bd21d06264 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/engine/OEngine.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.engine; + +import java.util.Map; + +import com.orientechnologies.orient.core.storage.OStorage; + +public interface OEngine { + String getName(); + + OStorage createStorage(String iURL, Map parameters); + + void removeStorage(OStorage iStorage); + + void shutdown(); + + /** + * Performs initialization of engine. + * Initialization of engine in constructor is prohibited and all initialization steps should be done in + * this method. + */ + void startup(); + + String getNameFromPath(String dbPath); + + /** + * @return {@code true} if this engine has been started and not shutdown yet, {@code false} otherwise. + */ + boolean isRunning(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/engine/OEngineAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/engine/OEngineAbstract.java new file mode 100755 index 00000000000..679fe5878b3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/engine/OEngineAbstract.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.engine; + +import java.util.Map; + +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.cache.OSnowFlakeIdGen; +import com.orientechnologies.orient.core.storage.cache.OWriteCacheIdGen; + +public abstract class OEngineAbstract implements OEngine { + private static final OWriteCacheIdGen writeCacheIdGen = new OSnowFlakeIdGen(); + + private boolean running = false; + + protected int generateStorageId() { + return writeCacheIdGen.nextId(); + } + + protected String getMode(Map iConfiguration) { + String dbMode = null; + if (iConfiguration != null) + dbMode = iConfiguration.get("mode"); + + if (dbMode == null) + dbMode = "rw"; + return dbMode; + } + + @Override + public void startup() { + this.running = true; + } + + @Override + public void shutdown() { + this.running = false; + } + + public void removeStorage(final OStorage iStorage) { + } + + @Override + public boolean isRunning() { + return running; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/engine/OMemoryAndLocalPaginatedEnginesInitializer.java b/core/src/main/java/com/orientechnologies/orient/core/engine/OMemoryAndLocalPaginatedEnginesInitializer.java new file mode 100644 index 00000000000..3da480bf358 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/engine/OMemoryAndLocalPaginatedEnginesInitializer.java @@ -0,0 +1,122 @@ +/* + * + * * Copyright 2016 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + */ + +package com.orientechnologies.orient.core.engine; + +import com.orientechnologies.common.io.OFileUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OMemory; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.storage.cache.local.twoq.O2QCache; + +/** + * Manages common initialization logic for memory and plocal engines. These engines are tight together through + * dependency to {@link com.orientechnologies.common.directmemory.OByteBufferPool}, which is hard to reconfigure + * if initialization logic is separate. + * + * @author Sergey Sitnikov + */ +public class OMemoryAndLocalPaginatedEnginesInitializer { + + /** + * Shared initializer instance. + */ + public static final OMemoryAndLocalPaginatedEnginesInitializer INSTANCE = new OMemoryAndLocalPaginatedEnginesInitializer(); + + private boolean initialized = false; + + /** + * Initializes common parts of memory and plocal engines if not initialized yet. Does nothing if engines already initialized. + */ + public void initialize() { + if (initialized) + return; + initialized = true; + + configureDefaults(); + + OMemory.checkDirectMemoryConfiguration(); + OMemory.checkByteBufferPoolConfiguration(); + OMemory.checkCacheMemoryConfiguration(); + + OMemory.fixCommonConfigurationProblems(); + } + + private void configureDefaults() { + if (System.getProperty(OGlobalConfiguration.DISK_CACHE_SIZE.getKey()) == null) + configureDefaultDiskCacheSize(); + + if (System.getProperty(OGlobalConfiguration.WAL_RESTORE_BATCH_SIZE.getKey()) == null) + configureDefaultWalRestoreBatchSize(); + } + + private void configureDefaultWalRestoreBatchSize() { + final long jvmMaxMemory = Runtime.getRuntime().maxMemory(); + if (jvmMaxMemory > 2L * OFileUtils.GIGABYTE) + // INCREASE WAL RESTORE BATCH SIZE TO 50K INSTEAD OF DEFAULT 1K + OGlobalConfiguration.WAL_RESTORE_BATCH_SIZE.setValue(50000); + else if (jvmMaxMemory > 512 * OFileUtils.MEGABYTE) + // INCREASE WAL RESTORE BATCH SIZE TO 10K INSTEAD OF DEFAULT 1K + OGlobalConfiguration.WAL_RESTORE_BATCH_SIZE.setValue(10000); + } + + private void configureDefaultDiskCacheSize() { + final long osMemory = OMemory.getPhysicalMemorySize(); + final long jvmMaxMemory = OMemory.getCappedRuntimeMaxMemory(2L * 1024 * 1024 * 1024 /* 2GB */); + final long maxDirectMemory = OMemory.getConfiguredMaxDirectMemory(); + + if (maxDirectMemory == -1) { + final long diskCacheInMB = jvmMaxMemory / 1024 / 1024; + OLogManager.instance().info(this, + "OrientDB auto-config DISKCACHE=%,dMB (heap=%,dMB direct=%,dMB os=%,dMB), assuming maximum direct memory size " + + "equals to maximum JVM heap size", diskCacheInMB, diskCacheInMB, diskCacheInMB, osMemory / 1024 / 1024); + OGlobalConfiguration.DISK_CACHE_SIZE.setValue(diskCacheInMB); + OGlobalConfiguration.MEMORY_CHUNK_SIZE + .setValue(Math.min(diskCacheInMB * 1024 * 1024, OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong())); + return; + } + + final long maxDirectMemoryInMB = maxDirectMemory / 1024 / 1024; + + // DISK-CACHE IN MB = OS MEMORY - MAX HEAP JVM MEMORY - 2 GB + long diskCacheInMB = (osMemory - jvmMaxMemory) / (1024 * 1024) - 2 * 1024; + if (diskCacheInMB > 0) { + diskCacheInMB = Math.min(diskCacheInMB, maxDirectMemoryInMB); + OLogManager.instance().info(this, "OrientDB auto-config DISKCACHE=%,dMB (heap=%,dMB direct=%,dMB os=%,dMB)", diskCacheInMB, + jvmMaxMemory / 1024 / 1024, maxDirectMemoryInMB, osMemory / 1024 / 1024); + + OGlobalConfiguration.DISK_CACHE_SIZE.setValue(diskCacheInMB); + OGlobalConfiguration.MEMORY_CHUNK_SIZE + .setValue(Math.min(diskCacheInMB * 1024 * 1024, OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong())); + } else { + // LOW MEMORY: SET IT TO 256MB ONLY + diskCacheInMB = Math.min(O2QCache.MIN_CACHE_SIZE, maxDirectMemoryInMB); + OLogManager.instance().warn(this, + "Not enough physical memory available for DISKCACHE: %,dMB (heap=%,dMB direct=%,dMB). Set lower Maximum Heap (-Xmx " + + "setting on JVM) and restart OrientDB. Now running with DISKCACHE=" + diskCacheInMB + "MB", osMemory / 1024 / 1024, + jvmMaxMemory / 1024 / 1024, maxDirectMemoryInMB); + OGlobalConfiguration.DISK_CACHE_SIZE.setValue(diskCacheInMB); + OGlobalConfiguration.MEMORY_CHUNK_SIZE + .setValue(Math.min(diskCacheInMB * 1024 * 1024, OGlobalConfiguration.MEMORY_CHUNK_SIZE.getValueAsLong())); + + OLogManager.instance().info(this, "OrientDB config DISKCACHE=%,dMB (heap=%,dMB direct=%,dMB os=%,dMB)", diskCacheInMB, + jvmMaxMemory / 1024 / 1024, maxDirectMemoryInMB, osMemory / 1024 / 1024); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/engine/local/OEngineLocalPaginated.java b/core/src/main/java/com/orientechnologies/orient/core/engine/local/OEngineLocalPaginated.java new file mode 100755 index 00000000000..6628c6e0eb6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/engine/local/OEngineLocalPaginated.java @@ -0,0 +1,128 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.engine.local; + +import com.orientechnologies.common.collection.closabledictionary.OClosableLinkedContainer; +import com.orientechnologies.common.directmemory.OByteBufferPool; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.engine.OEngineAbstract; +import com.orientechnologies.orient.core.engine.OMemoryAndLocalPaginatedEnginesInitializer; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.cache.local.twoq.O2QCache; +import com.orientechnologies.orient.core.storage.fs.OFileClassic; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; + +import java.util.Map; + +/** + * @author Andrey Lomakin + * @since 28.03.13 + */ +public class OEngineLocalPaginated extends OEngineAbstract { + public static final String NAME = "plocal"; + + private volatile O2QCache readCache; + + protected final OClosableLinkedContainer files = new OClosableLinkedContainer( + OGlobalConfiguration.OPEN_FILES_LIMIT.getValueAsInteger()); + + public OEngineLocalPaginated() { + } + + @Override + public void startup() { + OMemoryAndLocalPaginatedEnginesInitializer.INSTANCE.initialize(); + super.startup(); + + readCache = new O2QCache(calculateReadCacheMaxMemory(OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024), + OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024, true, + OGlobalConfiguration.DISK_CACHE_PINNED_PAGES.getValueAsInteger()); + + try { + if (OByteBufferPool.instance() != null) + OByteBufferPool.instance().registerMBean(); + } catch (Exception e) { + OLogManager.instance().error(this, "MBean for byte buffer pool cannot be registered", e); + } + } + + private long calculateReadCacheMaxMemory(final long cacheSize) { + return (long) (cacheSize * ((100 - OGlobalConfiguration.DISK_WRITE_CACHE_PART.getValueAsInteger()) / 100.0)); + } + + /** + * @param cacheSize Cache size in bytes. + * @see O2QCache#changeMaximumAmountOfMemory(long) + */ + public void changeCacheSize(final long cacheSize) { + if (readCache != null) + readCache.changeMaximumAmountOfMemory(calculateReadCacheMaxMemory(cacheSize)); + + //otherwise memory size will be set during cache initialization. + } + + public OStorage createStorage(final String dbName, final Map configuration) { + try { + + return new OLocalPaginatedStorage(dbName, dbName, getMode(configuration), generateStorageId(), readCache, files); + } catch (Exception e) { + final String message = + "Error on opening database: " + dbName + ". Current location is: " + new java.io.File(".").getAbsolutePath(); + OLogManager.instance().error(this, message, e); + + throw OException.wrapException(new ODatabaseException(message), e); + } + } + + public String getName() { + return NAME; + } + + public O2QCache getReadCache() { + return readCache; + } + + @Override + public String getNameFromPath(String dbPath) { + return OIOUtils.getRelativePathIfAny(dbPath, null); + } + + @Override + public void shutdown() { + try { + readCache.clear(); + files.clear(); + + try { + if (OByteBufferPool.instance() != null) + OByteBufferPool.instance().unregisterMBean(); + } catch (Exception e) { + OLogManager.instance().error(this, "MBean for byte buffer pool cannot be unregistered", e); + } + } finally { + super.shutdown(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/engine/memory/OEngineMemory.java b/core/src/main/java/com/orientechnologies/orient/core/engine/memory/OEngineMemory.java new file mode 100755 index 00000000000..7b7390927c6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/engine/memory/OEngineMemory.java @@ -0,0 +1,86 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.engine.memory; + +import com.orientechnologies.common.directmemory.OByteBufferPool; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.engine.OEngineAbstract; +import com.orientechnologies.orient.core.engine.OMemoryAndLocalPaginatedEnginesInitializer; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.memory.ODirectMemoryStorage; + +import java.util.Map; + +public class OEngineMemory extends OEngineAbstract { + public static final String NAME = "memory"; + + public OEngineMemory() { + } + + public OStorage createStorage(String url, Map configuration) { + try { + return new ODirectMemoryStorage(url, url, getMode(configuration), generateStorageId()); + } catch (Exception e) { + final String message = "Error on opening in memory storage: " + url; + OLogManager.instance().error(this, message, e); + + throw OException.wrapException(new ODatabaseException(message), e); + } + } + + public String getName() { + return NAME; + } + + @Override + public String getNameFromPath(String dbPath) { + return OIOUtils.getRelativePathIfAny(dbPath, null); + } + + @Override + public void startup() { + OMemoryAndLocalPaginatedEnginesInitializer.INSTANCE.initialize(); + super.startup(); + + try { + if (OByteBufferPool.instance() != null) + OByteBufferPool.instance().registerMBean(); + } catch (Exception e) { + OLogManager.instance().error(this, "MBean for byte buffer pool cannot be registered", e); + } + } + + @Override + public void shutdown() { + try { + try { + if (OByteBufferPool.instance() != null) + OByteBufferPool.instance().unregisterMBean(); + } catch (Exception e) { + OLogManager.instance().error(this, "MBean for byte buffer pool cannot be unregistered", e); + } + } finally { + super.shutdown(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/entity/OClassDictionary.java b/core/src/main/java/com/orientechnologies/orient/core/entity/OClassDictionary.java new file mode 100755 index 00000000000..7a8bc17a6a6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/entity/OClassDictionary.java @@ -0,0 +1,98 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.entity; + +import com.orientechnologies.orient.core.config.OStorageClusterHoleConfiguration; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.config.OStorageDataHoleConfiguration; +import com.orientechnologies.orient.core.config.OStorageFileConfiguration; +import com.orientechnologies.orient.core.config.OStorageSegmentConfiguration; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ORecordBytes; + +public class OClassDictionary { + private static final OClassDictionary instance = new OClassDictionary(); + + public static OClassDictionary instance() { + return instance; + } + + public Class getClassByCode(final char iType) { + switch (iType) { + case '0': + return ODocument.class; + case '2': + throw new IllegalArgumentException("Record type 'flat' is not supported anymore"); + case '3': + return ORecordBytes.class; + + case '4': + return OClass.class; + case '5': + return OProperty.class; + case '6': + return OUser.class; + + case '7': + return OStorageConfiguration.class; + case 'a': + return OStorageClusterHoleConfiguration.class; + case 'b': + return OStorageDataHoleConfiguration.class; + case 'c': + return OStorageSegmentConfiguration.class; + case 'd': + return OStorageFileConfiguration.class; + } + + throw new OConfigurationException("Unsupported record type: " + iType); + } + + public Character getCodeByClass(final Class iClass) { + if (iClass.equals(ODocument.class)) + return '0'; + if (iClass.equals(ORecordBytes.class)) + return '3'; + + if (iClass.equals(OClass.class)) + return '4'; + if (iClass.equals(OProperty.class)) + return '5'; + if (iClass.equals(OUser.class)) + return '6'; + + if (iClass.equals(OStorageConfiguration.class)) + return '7'; + if (iClass.equals(OStorageClusterHoleConfiguration.class)) + return 'a'; + if (iClass.equals(OStorageDataHoleConfiguration.class)) + return 'b'; + if (iClass.equals(OStorageSegmentConfiguration.class)) + return 'c'; + if (iClass.equals(OStorageFileConfiguration.class)) + return 'd'; + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManager.java b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManager.java new file mode 100755 index 00000000000..bad0fd4562c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManager.java @@ -0,0 +1,259 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.entity; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.reflection.OReflectionHelper; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OUser; + +public class OEntityManager { + private static Map databaseInstances = new HashMap(); + private OEntityManagerClassHandler classHandler = new OEntityManagerClassHandler(); + + protected OEntityManager() { + OLogManager.instance().debug(this, "Registering entity manager"); + + classHandler.registerEntityClass(OUser.class); + classHandler.registerEntityClass(ORole.class); + } + + public static synchronized OEntityManager getEntityManagerByDatabaseURL(final String iURL) { + OEntityManager instance = databaseInstances.get(iURL); + if (instance == null) { + instance = new OEntityManager(); + databaseInstances.put(iURL, instance); + } + return instance; + } + + /** + * Create a POJO by its class name. + * + * @see #registerEntityClasses(String) + */ + public synchronized Object createPojo(final String iClassName) throws OConfigurationException { + if (iClassName == null) + throw new IllegalArgumentException("Cannot create the object: class name is empty"); + + final Class entityClass = classHandler.getEntityClass(iClassName); + + try { + if (entityClass != null) + return createInstance(entityClass); + + } catch (Exception e) { + throw OException.wrapException(new OConfigurationException("Error while creating new pojo of class '" + iClassName + "'"), e); + } + + try { + // TRY TO INSTANTIATE THE CLASS DIRECTLY BY ITS NAME + return createInstance(Class.forName(iClassName)); + } catch (Exception e) { + throw OException.wrapException(new OConfigurationException("The class '" + iClassName + + "' was not found between the entity classes. Ensure registerEntityClasses(package) has been called first"), e); + } + } + + /** + * Returns the Java class by its name + * + * @param iClassName + * Simple class name without the package + * @return Returns the Java class by its name + */ + public synchronized Class getEntityClass(final String iClassName) { + return classHandler.getEntityClass(iClassName); + } + + public synchronized void deregisterEntityClass(final Class iClass) { + classHandler.deregisterEntityClass(iClass); + } + + public synchronized void deregisterEntityClasses(final String iPackageName) { + deregisterEntityClasses(iPackageName, Thread.currentThread().getContextClassLoader()); + } + + /** + * Scans all classes accessible from the context class loader which belong to the given package and subpackages. + * + * @param iPackageName + * The base package + */ + public synchronized void deregisterEntityClasses(final String iPackageName, final ClassLoader iClassLoader) { + OLogManager.instance().debug(this, "Discovering entity classes inside package: %s", iPackageName); + + List> classes = null; + try { + classes = OReflectionHelper.getClassesFor(iPackageName, iClassLoader); + } catch (ClassNotFoundException e) { + throw OException.wrapException(new ODatabaseException("Class cannot be found in package " + iPackageName), e); + } + for (Class c : classes) { + deregisterEntityClass(c); + } + + if (OLogManager.instance().isDebugEnabled()) { + for (Entry> entry : classHandler.getClassesEntrySet()) { + OLogManager.instance().debug(this, "Unloaded entity class '%s' from: %s", entry.getKey(), entry.getValue()); + } + } + } + + public synchronized void registerEntityClass(final Class iClass) { + registerEntityClass(iClass, true); + } + + public synchronized void registerEntityClass(final Class iClass, boolean forceSchemaReload) { + classHandler.registerEntityClass(iClass, forceSchemaReload); + } + + /** + * Registers provided classes + * + * @param iClassNames + * to be registered + */ + public synchronized void registerEntityClasses(final Collection iClassNames) { + registerEntityClasses(iClassNames, Thread.currentThread().getContextClassLoader()); + } + + /** + * Registers provided classes + * + * @param iClassNames + * to be registered + * @param iClassLoader + */ + public synchronized void registerEntityClasses(final Collection iClassNames, final ClassLoader iClassLoader) { + OLogManager.instance().debug(this, "Discovering entity classes for class names: %s", iClassNames); + + try { + registerEntityClasses(OReflectionHelper.getClassesFor(iClassNames, iClassLoader)); + } catch (ClassNotFoundException e) { + throw OException.wrapException(new ODatabaseException("Entity class cannot be found"), e); + } + } + + /** + * Scans all classes accessible from the context class loader which belong to the given package and subpackages. + * + * @param iPackageName + * The base package + */ + public synchronized void registerEntityClasses(final String iPackageName) { + registerEntityClasses(iPackageName, Thread.currentThread().getContextClassLoader()); + } + + /** + * Scans all classes accessible from the context class loader which belong to the given package and subpackages. + * + * @param iPackageName + * The base package + * @param iClassLoader + */ + public synchronized void registerEntityClasses(final String iPackageName, final ClassLoader iClassLoader) { + OLogManager.instance().debug(this, "Discovering entity classes inside package: %s", iPackageName); + + try { + registerEntityClasses(OReflectionHelper.getClassesFor(iPackageName, iClassLoader)); + } catch (ClassNotFoundException e) { + throw OException.wrapException(new ODatabaseException("Entity class cannot be found"), e); + } + } + + protected synchronized void registerEntityClasses(final List> classes) { + for (Class c : classes) { + if (!classHandler.containsEntityClass(c)) { + if (c.isAnonymousClass()) { + OLogManager.instance().debug(this, "Skip registration of anonymous class '%s'", c.getName()); + continue; + } + classHandler.registerEntityClass(c); + } + } + + if (OLogManager.instance().isDebugEnabled()) { + for (Entry> entry : classHandler.getClassesEntrySet()) { + OLogManager.instance().debug(this, "Loaded entity class '%s' from: %s", entry.getKey(), entry.getValue()); + } + } + } + + /** + * Scans all classes accessible from the context class loader which belong to the given class and all it's attributes - classes. + * + * @param aClass + * The class to start from + * @param recursive + * Beginning from the class, it will register all classes that are direct or indirect a attribute class + * + */ + public synchronized void registerEntityClasses(Class aClass, boolean recursive) { + if (recursive) { + classHandler.registerEntityClass(aClass); + Field[] declaredFields = aClass.getDeclaredFields(); + for (Field declaredField : declaredFields) { + Class declaredFieldType = declaredField.getType(); + if (!classHandler.containsEntityClass(declaredFieldType)) { + registerEntityClasses(declaredFieldType, recursive); + } + } + } else { + classHandler.registerEntityClass(aClass); + } + } + + /** + * Sets the received handler as default and merges the classes all together. + * + * @param iClassHandler + */ + public synchronized void setClassHandler(final OEntityManagerClassHandler iClassHandler) { + Iterator>> iterator = classHandler.getClassesEntrySet().iterator(); + while (iterator.hasNext()){ + Entry> entry = iterator.next(); + boolean forceSchemaReload = !iterator.hasNext(); + iClassHandler.registerEntityClass(entry.getValue(), forceSchemaReload); + } + this.classHandler = iClassHandler; + } + + public synchronized Collection> getRegisteredEntities() { + return classHandler.getRegisteredEntities(); + } + + protected Object createInstance(final Class iClass) + throws InstantiationException, IllegalAccessException, InvocationTargetException { + return classHandler.createInstance(iClass); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerClassHandler.java b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerClassHandler.java new file mode 100644 index 00000000000..4e6be49b847 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerClassHandler.java @@ -0,0 +1,106 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.entity; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * @author luca.molino + * + */ +public class OEntityManagerClassHandler { + + private Map> entityClasses = new HashMap>(); + + /** + * Returns the Java class by its name + * + * @param iClassName + * Simple class name without the package + * @return Returns the Java class by its name + */ + public synchronized Class getEntityClass(final String iClassName) { + return entityClasses.get(iClassName); + } + + public synchronized void registerEntityClass(final Class iClass) { + entityClasses.put(iClass.getSimpleName(), iClass); + } + + public synchronized void registerEntityClass(final Class iClass, boolean forceSchemaReload) { + entityClasses.put(iClass.getSimpleName(), iClass); + } + + public synchronized void registerEntityClass(final String iClassName, final Class iClass) { + entityClasses.put(iClassName, iClass); + } + + public synchronized void registerEntityClass(final String iClassName, final Class iClass, boolean forceSchemaReload) { + entityClasses.put(iClassName, iClass); + } + + public synchronized void deregisterEntityClass(final String iClassName) { + entityClasses.remove(iClassName); + } + + public synchronized void deregisterEntityClass(final Class iClass) { + entityClasses.remove(iClass.getSimpleName()); + } + + public synchronized Set>> getClassesEntrySet() { + return entityClasses.entrySet(); + } + + public synchronized boolean containsEntityClass(final String iClassName) { + return entityClasses.containsKey(iClassName); + } + + public synchronized boolean containsEntityClass(final Class iClass) { + return entityClasses.containsKey(iClass.getSimpleName()); + } + + public synchronized Object createInstance(final Class iClass) throws InstantiationException, IllegalAccessException, + InvocationTargetException { + Constructor defaultConstructor = null; + for (Constructor c : iClass.getConstructors()) { + if (c.getParameterTypes().length == 0) { + defaultConstructor = c; + break; + } + } + + if (defaultConstructor == null) + throw new IllegalArgumentException("Cannot create an object of class '" + iClass.getName() + + "' because it has no default constructor. Please define the method: " + iClass.getSimpleName() + "()"); + + if (!defaultConstructor.isAccessible()) + // OVERRIDE PROTECTION + defaultConstructor.setAccessible(true); + + return defaultConstructor.newInstance(); + } + + public synchronized Collection> getRegisteredEntities() { + return entityClasses.values(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerInternal.java b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerInternal.java new file mode 100644 index 00000000000..455aac9a851 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/entity/OEntityManagerInternal.java @@ -0,0 +1,24 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.entity; + +public class OEntityManagerInternal extends OEntityManager { + public static final OEntityManagerInternal INSTANCE = new OEntityManagerInternal(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OAllCacheEntriesAreUsedException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OAllCacheEntriesAreUsedException.java new file mode 100755 index 00000000000..d9358e04151 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OAllCacheEntriesAreUsedException.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.exception; + +public class OAllCacheEntriesAreUsedException extends ODatabaseException { + + public OAllCacheEntriesAreUsedException(OAllCacheEntriesAreUsedException exception) { + super(exception); + } + + public OAllCacheEntriesAreUsedException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OBackupInProgressException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OBackupInProgressException.java new file mode 100755 index 00000000000..10596533acb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OBackupInProgressException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.common.exception.OHighLevelException; + +/** + * @author Andrey Lomakin . + * @since 10/5/2015 + */ +public class OBackupInProgressException extends OCoreException implements OHighLevelException { + public OBackupInProgressException(OBackupInProgressException exception) { + super(exception); + } + + public OBackupInProgressException(String message, String componentName, OErrorCode errorCode) { + super(message, componentName, errorCode); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OClusterPositionMapException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OClusterPositionMapException.java new file mode 100755 index 00000000000..e76db03a558 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OClusterPositionMapException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.OClusterPositionMap; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public class OClusterPositionMapException extends ODurableComponentException { + public OClusterPositionMapException(OClusterPositionMapException exception) { + super(exception); + } + + public OClusterPositionMapException(String message, OClusterPositionMap component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandExecutionException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandExecutionException.java new file mode 100755 index 00000000000..77cef4e11ac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandExecutionException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OCommandExecutionException extends OCoreException { + + private static final long serialVersionUID = -7430575036316163711L; + + public OCommandExecutionException(OCommandExecutionException exception) { + super(exception); + } + + public OCommandExecutionException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandInterruptedException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandInterruptedException.java new file mode 100755 index 00000000000..643b357b1e3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OCommandInterruptedException.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.concur.ONeedRetryException; + +/** + * Exception thrown in case the execution of the command has been interrupted. + * + * @author Luca Garulli + */ +public class OCommandInterruptedException extends ONeedRetryException { + + private static final long serialVersionUID = -7430575036316163711L; + + public OCommandInterruptedException(OCommandInterruptedException exception) { + super(exception); + } + + public OCommandInterruptedException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentCreateException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentCreateException.java new file mode 100755 index 00000000000..77194657b13 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentCreateException.java @@ -0,0 +1,85 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.id.ORID; + +/** + * Exception thrown when a create operation get a non expected RID. This could happen with distributed inserts. The client should + * retry to re-execute the operation. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + * + */ +public class OConcurrentCreateException extends ONeedRetryException implements OHighLevelException { + + private static final long serialVersionUID = 1L; + + private ORID expectedRid; + private ORID actualRid; + + public OConcurrentCreateException(OConcurrentCreateException exception) { + super(exception); + + this.expectedRid = exception.expectedRid; + this.actualRid = exception.actualRid; + } + + protected OConcurrentCreateException(final String message) { + super(message); + } + + public OConcurrentCreateException(final ORID expectedRID, final ORID actualRid) { + super(makeMessage(expectedRID, actualRid)); + + this.expectedRid = expectedRID; + this.actualRid = actualRid; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof OConcurrentCreateException)) + return false; + + final OConcurrentCreateException other = (OConcurrentCreateException) obj; + + return expectedRid.equals(other.expectedRid) && actualRid.equals(other.actualRid); + } + + public ORID getExpectedRid() { + return expectedRid; + } + + public ORID getActualRid() { + return actualRid; + } + + private static String makeMessage(ORID expectedRid, ORID actualRid) { + final StringBuilder sb = new StringBuilder(); + sb.append("Cannot create the record "); + sb.append(expectedRid); + sb.append(" because the assigned RID was "); + sb.append(actualRid); + sb.append(" instead"); + return sb.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentModificationException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentModificationException.java new file mode 100755 index 00000000000..82041e7ddc0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OConcurrentModificationException.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.id.ORID; + +import java.util.Locale; + +/** + * Exception thrown when MVCC is enabled and a record cannot be updated or deleted because versions don't match. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OConcurrentModificationException extends ONeedRetryException implements OHighLevelException { + + private static final long serialVersionUID = 1L; + + private ORID rid; + private int databaseVersion = 0; + private int recordVersion = 0; + private int recordOperation; + + public OConcurrentModificationException(OConcurrentModificationException exception) { + super(exception); + + this.rid = exception.rid; + this.recordVersion = exception.recordVersion; + this.databaseVersion = exception.databaseVersion; + this.recordOperation = exception.recordOperation; + } + + protected OConcurrentModificationException(final String message) { + super(message); + } + + public OConcurrentModificationException(final ORID iRID, final int iDatabaseVersion, final int iRecordVersion, + final int iRecordOperation) { + super(makeMessage(iRecordOperation, iRID, iDatabaseVersion, iRecordVersion)); + + if (OFastConcurrentModificationException.enabled()) + throw new IllegalStateException("Fast-throw is enabled. Use OFastConcurrentModificationException.instance() instead"); + + rid = iRID; + databaseVersion = iDatabaseVersion; + recordVersion = iRecordVersion; + recordOperation = iRecordOperation; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof OConcurrentModificationException)) + return false; + + final OConcurrentModificationException other = (OConcurrentModificationException) obj; + + if (recordOperation == other.recordOperation && rid.equals(other.rid)) { + if (databaseVersion == other.databaseVersion) + return recordOperation == other.recordOperation; + } + + return false; + } + + public int getEnhancedDatabaseVersion() { + return databaseVersion; + } + + public int getEnhancedRecordVersion() { + return recordVersion; + } + + public ORID getRid() { + return rid; + } + + private static String makeMessage(int recordOperation, ORID rid, int databaseVersion, int recordVersion) { + final String operation = ORecordOperation.getName(recordOperation); + + final StringBuilder sb = new StringBuilder(); + sb.append("Cannot "); + sb.append(operation); + sb.append(" the record "); + sb.append(rid); + sb.append(" because the version is not the latest. Probably you are "); + sb.append(operation.toLowerCase(Locale.ENGLISH).substring(0, operation.length() - 1)); + sb.append("ing an old record or it has been modified by another user (db=v"); + sb.append(databaseVersion); + sb.append(" your=v"); + sb.append(recordVersion); + sb.append(")"); + return sb.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OConfigurationException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OConfigurationException.java new file mode 100755 index 00000000000..dc814f782dd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OConfigurationException.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OConfigurationException extends OCoreException { + + private static final long serialVersionUID = -8486291378415776372L; + + public OConfigurationException(OConfigurationException exception) { + super(exception); + } + + public OConfigurationException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OCoreException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OCoreException.java new file mode 100755 index 00000000000..79d1592f894 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OCoreException.java @@ -0,0 +1,80 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; + +/** + * @author Andrey Lomakin . + * @since 9/28/2015 + */ +public abstract class OCoreException extends OException { + private OErrorCode errorCode; + + private final String dbName; + private final String componentName; + + public OCoreException(final OCoreException exception) { + super(exception); + this.dbName = exception.dbName; + this.componentName = exception.componentName; + } + + public OCoreException(final String message) { + this(message, null, null); + } + + public OCoreException(final String message, final String componentName) { + this(message, componentName, null); + + } + + public OCoreException(final String message, final String componentName, final OErrorCode errorCode) { + super(message); + + this.errorCode = errorCode; + + if (componentName != null) { + this.componentName = componentName; + } else { + this.componentName = null; + } + + final ODatabaseDocumentInternal database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (database != null) { + dbName = database.getName(); + } else { + dbName = null; + } + + } + + public OErrorCode getErrorCode() { + return errorCode; + } + + public String getDbName() { + return dbName; + } + + public String getComponentName() { + return componentName; + } + + @Override + public final String getMessage() { + final StringBuilder builder = new StringBuilder(super.getMessage()); + if (dbName != null) { + builder.append("\r\n\t").append("DB name=\"").append(dbName).append("\""); + } + if (componentName != null) { + builder.append("\r\n\t").append("Component Name=\"").append(componentName).append("\""); + } + if (errorCode != null) { + builder.append("\r\n\t").append("Error Code=\"").append(errorCode.getCode()).append("\""); + } + + return builder.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/ODatabaseException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/ODatabaseException.java new file mode 100755 index 00000000000..23f71e445cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/ODatabaseException.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OException; + +public class ODatabaseException extends OCoreException { + + private static final long serialVersionUID = -2655748565531836968L; + + public ODatabaseException(ODatabaseException exception) { + super(exception); + } + + public ODatabaseException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/ODurableComponentException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/ODurableComponentException.java new file mode 100755 index 00000000000..3bb7888c1a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/ODurableComponentException.java @@ -0,0 +1,17 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public abstract class ODurableComponentException extends OCoreException { + public ODurableComponentException(ODurableComponentException exception) { + super(exception); + } + + public ODurableComponentException(String message, ODurableComponent component) { + super(message, component.getName()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OFastConcurrentModificationException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OFastConcurrentModificationException.java new file mode 100755 index 00000000000..872bff348a5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OFastConcurrentModificationException.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; + +/** + * Exception thrown when MVCC is enabled and a record cannot be updated or deleted because versions don't match. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OFastConcurrentModificationException extends OConcurrentModificationException { + private static final long serialVersionUID = 1L; + + private static final OGlobalConfiguration CONFIG = OGlobalConfiguration.DB_MVCC_THROWFAST; + private static final boolean ENABLED = CONFIG.getValueAsBoolean(); + private static final String MESSAGE = "This is a fast-thrown exception. Disable " + + CONFIG.getKey() + + " to see full exception stacktrace and message."; + + private static final OFastConcurrentModificationException INSTANCE = new OFastConcurrentModificationException(); + + public OFastConcurrentModificationException(OFastConcurrentModificationException exception) { + super(exception); + } + + private OFastConcurrentModificationException() { + super(MESSAGE); + } + + public static boolean enabled() { + return ENABLED; + } + + public static OFastConcurrentModificationException instance() { + return INSTANCE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OFetchException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OFetchException.java new file mode 100755 index 00000000000..128c690df24 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OFetchException.java @@ -0,0 +1,35 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OException; + +/** + * @author luca.molino + * + */ +public class OFetchException extends OCoreException { + private static final long serialVersionUID = 7247597939953323863L; + + public OFetchException(OFetchException exception) { + super(exception); + } + + public OFetchException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OFileLockedByAnotherProcessException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OFileLockedByAnotherProcessException.java new file mode 100644 index 00000000000..b06742a5f5e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OFileLockedByAnotherProcessException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OHighLevelException; + +/** + * This exception is thrown if several processes try to access the same storage directory. + * It is prohibited because may lead to data corruption. + */ +public class OFileLockedByAnotherProcessException extends OException implements OHighLevelException { + public OFileLockedByAnotherProcessException(String message) { + super(message); + } + + public OFileLockedByAnotherProcessException(OFileLockedByAnotherProcessException exception) { + super(exception); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OHashTableDirectoryException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OHashTableDirectoryException.java new file mode 100755 index 00000000000..a3ef41dd718 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OHashTableDirectoryException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.index.hashindex.local.OHashTableDirectory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public class OHashTableDirectoryException extends ODurableComponentException { + public OHashTableDirectoryException(OHashTableDirectoryException exception) { + super(exception); + } + + public OHashTableDirectoryException(String message, OHashTableDirectory component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OIndexIsRebuildingException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OIndexIsRebuildingException.java new file mode 100644 index 00000000000..672dfc56178 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OIndexIsRebuildingException.java @@ -0,0 +1,33 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.exception; + +/** + * Exception which is thrown by {@link com.orientechnologies.orient.core.index.OIndexChangesWrapper} + * if index which is related to wrapped cursor is being rebuilt. + * + * @see com.orientechnologies.orient.core.index.OIndexAbstract#getRebuildVersion() + */ +public class OIndexIsRebuildingException extends ORetryQueryException { + public OIndexIsRebuildingException(OIndexIsRebuildingException exception) { + super(exception); + } + + public OIndexIsRebuildingException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidIndexEngineIdException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidIndexEngineIdException.java new file mode 100644 index 00000000000..2284ebebfb5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidIndexEngineIdException.java @@ -0,0 +1,27 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.exception; + +/** + * Special type of exception which indicates that invalid index id was passed into + * storage and index data should be reloaded + */ +public class OInvalidIndexEngineIdException extends Exception { + public OInvalidIndexEngineIdException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidStorageEncryptionKeyException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidStorageEncryptionKeyException.java new file mode 100755 index 00000000000..2a24bf3f4e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OInvalidStorageEncryptionKeyException.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +/** + * Storage key is invalid. Used in cryptography. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("serial") +public class OInvalidStorageEncryptionKeyException extends OSecurityException { + + public OInvalidStorageEncryptionKeyException(OInvalidStorageEncryptionKeyException exception) { + super(exception); + } + + public OInvalidStorageEncryptionKeyException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OLoadCacheStateException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OLoadCacheStateException.java new file mode 100755 index 00000000000..d6747d3240c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OLoadCacheStateException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.orient.core.storage.cache.OWriteCache; + +/** + * This exception is thrown if it is impossible to read data from file which contains state of 2Q cache. + * + * @see com.orientechnologies.orient.core.storage.cache.local.twoq.O2QCache#loadCacheState(OWriteCache) + */ +public class OLoadCacheStateException extends OSystemException { + public OLoadCacheStateException(OSystemException exception) { + super(exception); + } + + public OLoadCacheStateException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OLocalHashTableException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OLocalHashTableException.java new file mode 100755 index 00000000000..be5e11c7f9f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OLocalHashTableException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.index.hashindex.local.OLocalHashTable; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public class OLocalHashTableException extends ODurableComponentException { + public OLocalHashTableException(OLocalHashTableException exception) { + super(exception); + } + + public OLocalHashTableException(String message, OLocalHashTable component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OLowDiskSpaceException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OLowDiskSpaceException.java new file mode 100755 index 00000000000..728f2eb9bf4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OLowDiskSpaceException.java @@ -0,0 +1,30 @@ +/* + * + * Licensed 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 com.orientechnologies.orient.core.exception; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 10/6/14 + */ +public class OLowDiskSpaceException extends OStorageException { + public OLowDiskSpaceException(OLowDiskSpaceException exception) { + super(exception); + } + + public OLowDiskSpaceException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OPaginatedClusterException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OPaginatedClusterException.java new file mode 100755 index 00000000000..7f2ae1a445a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OPaginatedClusterException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.storage.impl.local.paginated.OPaginatedCluster; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public class OPaginatedClusterException extends ODurableComponentException { + public OPaginatedClusterException(OPaginatedClusterException exception) { + super(exception); + } + + public OPaginatedClusterException(String message, OPaginatedCluster component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OQueryParsingException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OQueryParsingException.java new file mode 100755 index 00000000000..dc368817505 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OQueryParsingException.java @@ -0,0 +1,74 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; + +public class OQueryParsingException extends OCommandSQLParsingException { + + private String text; + private int position = -1; + private static final long serialVersionUID = -7430575036316163711L; + + private static String makeMessage(int position, String text, String message) { + StringBuilder buffer = new StringBuilder(); + if (position > -1) { + buffer.append("Error on parsing query at position #"); + buffer.append(position); + buffer.append(": "); + } + + buffer.append(message); + + if (text != null) { + buffer.append("\nQuery: "); + buffer.append(text); + buffer.append("\n------"); + for (int i = 0; i < position - 1; ++i) + buffer.append("-"); + + buffer.append("^"); + } + return buffer.toString(); + } + + public OQueryParsingException(OQueryParsingException exception) { + super(exception); + + this.text = exception.text; + this.position = exception.position; + } + + public OQueryParsingException(final String iMessage) { + super(iMessage); + } + + public OQueryParsingException(final String iMessage, final String iText, final int iPosition) { + super(makeMessage(iPosition, iText, iMessage)); + + text = iText; + position = iPosition; + } + + public String getText() { + return text; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OReadCacheException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OReadCacheException.java new file mode 100755 index 00000000000..893e48292f0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OReadCacheException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 9/28/2015 + */ +public class OReadCacheException extends OCoreException { + + public OReadCacheException(OReadCacheException exception) { + super(exception); + } + + public OReadCacheException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordContentNotFoundException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordContentNotFoundException.java new file mode 100755 index 00000000000..30502cc5e71 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordContentNotFoundException.java @@ -0,0 +1,62 @@ +/* + * + * * Copyright 2016 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +/** + * Indicates that the requested record content was not found in the database. Typically, this happens when the record was deleted. + */ +public class ORecordContentNotFoundException extends OCoreException implements OHighLevelException { + private static final long serialVersionUID = 1; + + private final Object context; + + /** + * Constructs a new instance of this exception class from another instance of it. Implicitly used by the network deserialization. + * + * @param exception the exception instance to construct the new one from. + */ + @SuppressWarnings("unused") + public ORecordContentNotFoundException(ORecordContentNotFoundException exception) { + super(exception); + this.context = exception.context; + } + + /** + * Constructs a new instance of this exception class for the provided context object. + * + * @param context the context object. Since the actual record maybe not known at this point, but there is still a need to provide + * some context to distinguish this exception from the others. + */ + public ORecordContentNotFoundException(Object context) { + super("Unable to find record content. The record or its content was deleted. Context: " + context); + this.context = context; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof ORecordContentNotFoundException)) + return false; + final ORecordContentNotFoundException other = (ORecordContentNotFoundException) obj; + + return context == other.context || context != null && context.equals(other.context); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordNotFoundException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordNotFoundException.java new file mode 100755 index 00000000000..93479539b01 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/ORecordNotFoundException.java @@ -0,0 +1,59 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.id.ORID; + +public class ORecordNotFoundException extends OCoreException implements OHighLevelException { + + private static final long serialVersionUID = -265573123216968L; + private ORID rid; + + public ORecordNotFoundException(final ORecordNotFoundException exception) { + super(exception); + this.rid = exception.rid; + } + + public ORecordNotFoundException(final ORID iRID) { + super("The record with id '" + iRID + "' was not found"); + rid = iRID; + } + + public ORecordNotFoundException(final ORID iRID, final String message) { + super(message); + rid = iRID; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof ORecordNotFoundException)) + return false; + + if (rid == null && ((ORecordNotFoundException) obj).rid == null) + return toString().equals(obj.toString()); + + return rid != null ? rid.equals(((ORecordNotFoundException) obj).rid) : ((ORecordNotFoundException) obj).rid.equals(rid); + } + + public ORID getRid() { + return rid; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/ORetryQueryException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/ORetryQueryException.java new file mode 100644 index 00000000000..4cf9a71bfc0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/ORetryQueryException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +/** + * Exception which is thrown by core components to ask command handler + * {@link com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage#command(com.orientechnologies.orient.core.command.OCommandRequestText)} + * to rebuild and run executed command again. + * + * @see com.orientechnologies.orient.core.index.OIndexAbstract#getRebuildVersion() + */ +public abstract class ORetryQueryException extends OCoreException { + public ORetryQueryException(ORetryQueryException exception) { + super(exception); + } + + public ORetryQueryException(String message) { + super(message); + } +} + diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSBTreeBonsaiLocalException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSBTreeBonsaiLocalException.java new file mode 100755 index 00000000000..f5f41cd1e67 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSBTreeBonsaiLocalException.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsaiLocal; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 10/2/2015 + */ +public class OSBTreeBonsaiLocalException extends ODurableComponentException { + public OSBTreeBonsaiLocalException(OSBTreeBonsaiLocalException exception) { + super(exception); + } + + public OSBTreeBonsaiLocalException(String message, OSBTreeBonsaiLocal component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaException.java new file mode 100755 index 00000000000..55cbdf2c4cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaException.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +public class OSchemaException extends OCoreException implements OHighLevelException { + + private static final long serialVersionUID = -8486291378415776372L; + + public OSchemaException(OSchemaException exception) { + super(exception); + } + + public OSchemaException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaNotCreatedException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaNotCreatedException.java new file mode 100644 index 00000000000..858eb5b4eb0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSchemaNotCreatedException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +public class OSchemaNotCreatedException extends OSchemaException implements OHighLevelException { + public OSchemaNotCreatedException(String message) { + super(message); + } + + /** + * This constructor is needed to restore and reproduce exception on client side in case of remote storage exception handling. + * Please create "copy constructor" for each exception which has current one as a parent. + * + * @param exception + */ + public OSchemaNotCreatedException(OSchemaNotCreatedException exception) { + super(exception); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityAccessException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityAccessException.java new file mode 100755 index 00000000000..1251b60f650 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityAccessException.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +public class OSecurityAccessException extends OSecurityException implements OHighLevelException { + + private static final long serialVersionUID = -8486291378415776372L; + private String databaseName; + + public OSecurityAccessException(OSecurityAccessException exception) { + super(exception); + + this.databaseName = exception.databaseName; + } + + public OSecurityAccessException(final String iDatabasename, final String message) { + super(message); + databaseName = iDatabasename; + } + + public OSecurityAccessException(final String message) { + super(message); + } + + public String getDatabaseName() { + return databaseName; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityException.java new file mode 100755 index 00000000000..166976d8760 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSecurityException.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * Generic Security exception. Used in cryptography. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +@SuppressWarnings("serial") +public class OSecurityException extends OCoreException implements OHighLevelException { + public OSecurityException(OSecurityException exception) { + super(exception); + } + + public OSecurityException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSequenceException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSequenceException.java new file mode 100755 index 00000000000..f06d771e3d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSequenceException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OException; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 2/28/2015 + */ +public class OSequenceException extends OCoreException { + private static final long serialVersionUID = -2719447287841577672L; + + public OSequenceException(OSequenceException exception) { + super(exception); + } + + public OSequenceException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OSerializationException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OSerializationException.java new file mode 100755 index 00000000000..0a91635a39d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OSerializationException.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OSerializationException extends OCoreException { + + private static final long serialVersionUID = -3003977236233691448L; + + public OSerializationException(OSerializationException exception) { + super(exception); + } + + public OSerializationException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageException.java new file mode 100755 index 00000000000..f35f00c7d7c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageException.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OStorageException extends OCoreException { + + private static final long serialVersionUID = -2655748565531836968L; + + public OStorageException(OStorageException exception) { + super(exception); + } + + public OStorageException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageExistsException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageExistsException.java new file mode 100644 index 00000000000..90f01fde462 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OStorageExistsException.java @@ -0,0 +1,13 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +public class OStorageExistsException extends OStorageException implements OHighLevelException { + public OStorageExistsException(OStorageExistsException exception) { + super(exception); + } + + public OStorageExistsException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OTooBigIndexKeyException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OTooBigIndexKeyException.java new file mode 100644 index 00000000000..b71387432a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OTooBigIndexKeyException.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +/** + * This exception is thrown when key size exceeds allowed limits + */ +public class OTooBigIndexKeyException extends OCoreException implements OHighLevelException { + public OTooBigIndexKeyException(OCoreException exception) { + super(exception); + } + + public OTooBigIndexKeyException(String message, String componentName) { + super(message, componentName); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionAbortedException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionAbortedException.java new file mode 100755 index 00000000000..b89e30fb4c9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionAbortedException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OTransactionAbortedException extends OTransactionException { + + private static final long serialVersionUID = 2347493191705052402L; + + public OTransactionAbortedException(OTransactionAbortedException exception) { + super(exception); + } + + public OTransactionAbortedException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionBlockedException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionBlockedException.java new file mode 100755 index 00000000000..5120720439f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionBlockedException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OTransactionBlockedException extends OTransactionException { + + private static final long serialVersionUID = 2347493191705052402L; + + public OTransactionBlockedException(OTransactionBlockedException exception) { + super(exception); + } + + public OTransactionBlockedException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionException.java new file mode 100755 index 00000000000..852b57f8740 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OTransactionException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +public class OTransactionException extends OCoreException { + + private static final long serialVersionUID = 2347493191705052402L; + + public OTransactionException(OTransactionException exception) { + super(exception); + } + + public OTransactionException(String message) { + super(message); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OValidationException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OValidationException.java new file mode 100755 index 00000000000..598015b38cb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OValidationException.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OHighLevelException; + +@SuppressWarnings("serial") +public class OValidationException extends OCoreException implements OHighLevelException { + + public OValidationException(OValidationException exception) { + super(exception); + } + + public OValidationException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/exception/OWriteCacheException.java b/core/src/main/java/com/orientechnologies/orient/core/exception/OWriteCacheException.java new file mode 100755 index 00000000000..8b6fbced7c3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/exception/OWriteCacheException.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.exception; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin . + * @since 9/28/2015 + */ +public class OWriteCacheException extends OCoreException { + + public OWriteCacheException(OWriteCacheException exception) { + super(exception); + } + + public OWriteCacheException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchContext.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchContext.java new file mode 100755 index 00000000000..6764969b0f8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchContext.java @@ -0,0 +1,63 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.fetch; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author luca.molino + * + */ +public interface OFetchContext { + + public void onBeforeFetch(final ODocument iRootRecord) throws OFetchException; + + public void onAfterFetch(final ODocument iRootRecord) throws OFetchException; + + public void onBeforeArray(final ODocument iRootRecord, final String iFieldName, final Object iUserObject, + final OIdentifiable[] iArray) throws OFetchException; + + public void onAfterArray(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) + throws OFetchException; + + public void onBeforeCollection(final ODocument iRootRecord, final String iFieldName, final Object iUserObject, + final Iterable iterable) throws OFetchException; + + public void onAfterCollection(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) + throws OFetchException; + + public void onBeforeMap(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) + throws OFetchException; + + public void onAfterMap(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) + throws OFetchException; + + public void onBeforeDocument(final ODocument iRecord, final ODocument iDocument, final String iFieldName, + final Object iUserObject) throws OFetchException; + + public void onAfterDocument(final ODocument iRootRecord, final ODocument iDocument, final String iFieldName, + final Object iUserObject) throws OFetchException; + + public void onBeforeStandardField(final Object iFieldValue, final String iFieldName, final Object iUserObject, OType fieldType); + + public void onAfterStandardField(final Object iFieldValue, final String iFieldName, final Object iUserObject, OType fieldType); + + public boolean fetchEmbeddedDocuments(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchHelper.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchHelper.java new file mode 100755 index 00000000000..a9d212a135b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchHelper.java @@ -0,0 +1,594 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.fetch; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.fetch.json.OJSONFetchContext; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.*; + +/** + * Helper class for fetching. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @author Luca Molino + * @author Claudio Tesoriero (giastfader @ github) + */ +public class OFetchHelper { + public static final String DEFAULT = "*:0"; + public static final OFetchPlan DEFAULT_FETCHPLAN = new OFetchPlan(DEFAULT); + + public static OFetchPlan buildFetchPlan(final String iFetchPlan) { + if (iFetchPlan == null) + return null; + + if (DEFAULT.equals(iFetchPlan)) + return DEFAULT_FETCHPLAN; + + return new OFetchPlan(iFetchPlan); + } + + public static void fetch(final ORecord iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + final OFetchListener iListener, final OFetchContext iContext, final String iFormat) { + try { + if (iRootRecord instanceof ODocument) { + // SCHEMA AWARE + final ODocument record = (ODocument) iRootRecord; + final Map parsedRecords = new HashMap(); + + final boolean isEmbedded = record.isEmbedded() || !record.getIdentity().isPersistent(); + if (!isEmbedded) + parsedRecords.put(iRootRecord.getIdentity(), 0); + + if (!iFormat.contains("shallow")) + processRecordRidMap(record, iFetchPlan, 0, 0, -1, parsedRecords, "", iContext); + + processRecord(record, iUserObject, iFetchPlan, 0, 0, -1, parsedRecords, "", iListener, iContext, iFormat); + } + } catch (Exception e) { + OLogManager.instance().error(null, "Fetching error on record %s", e, iRootRecord.getIdentity()); + } + } + + public static void checkFetchPlanValid(final String iFetchPlan) { + + if (iFetchPlan != null && !iFetchPlan.isEmpty()) { + // CHECK IF THERE IS SOME FETCH-DEPTH + final List planParts = OStringSerializerHelper.split(iFetchPlan, ' '); + if (!planParts.isEmpty()) { + for (String planPart : planParts) { + final List parts = OStringSerializerHelper.split(planPart, ':'); + if (parts.size() != 2) { + throw new IllegalArgumentException("Fetch plan '" + iFetchPlan + "' is invalid"); + } + } + } else { + throw new IllegalArgumentException("Fetch plan '" + iFetchPlan + "' is invalid"); + } + } + + } + + public static boolean isFetchPlanValid(final String iFetchPlan) { + + if (iFetchPlan != null && !iFetchPlan.isEmpty()) { + // CHECK IF THERE IS SOME FETCH-DEPTH + final List planParts = OStringSerializerHelper.split(iFetchPlan, ' '); + if (!planParts.isEmpty()) { + for (String planPart : planParts) { + final List parts = OStringSerializerHelper.split(planPart, ':'); + if (parts.size() != 2) { + return false; + } + } + } else { + return false; + } + } + + return true; + + } + + private static int getDepthLevel(final OFetchPlan iFetchPlan, final String iFieldPath, final int iCurrentLevel) { + if (iFetchPlan == null) + return 0; + return iFetchPlan.getDepthLevel(iFieldPath, iCurrentLevel); + } + + public static void processRecordRidMap(final ODocument record, final OFetchPlan iFetchPlan, final int iCurrentLevel, + final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchContext iContext) throws IOException { + if (iFetchPlan == null) + return; + + if (iFetchPlan == OFetchHelper.DEFAULT_FETCHPLAN) + return; + + Object fieldValue; + for (String fieldName : record.fieldNames()) { + int depthLevel; + final String fieldPath = !iFieldPathFromRoot.isEmpty() ? iFieldPathFromRoot + "." + fieldName : fieldName; + + depthLevel = getDepthLevel(iFetchPlan, fieldPath, iCurrentLevel); + if (depthLevel == -2) + continue; + if (iFieldDepthLevel > -1) + depthLevel = iFieldDepthLevel; + + fieldValue = record.rawField(fieldName); + if (fieldValue == null || !(fieldValue instanceof OIdentifiable) + && (!(fieldValue instanceof ORecordLazyMultiValue) || !((ORecordLazyMultiValue) fieldValue).rawIterator().hasNext() + || !(((ORecordLazyMultiValue) fieldValue).rawIterator().next() instanceof OIdentifiable)) + && (!(fieldValue instanceof Collection) || ((Collection) fieldValue).size() == 0 + || !(((Collection) fieldValue).iterator().next() instanceof OIdentifiable)) + && (!(fieldValue.getClass().isArray()) || Array.getLength(fieldValue) == 0 + || !(Array.get(fieldValue, 0) instanceof OIdentifiable)) + && (!(fieldValue instanceof OMultiCollectionIterator)) + && (!(fieldValue instanceof Map) || ((Map) fieldValue).size() == 0 + || !(((Map) fieldValue).values().iterator().next() instanceof OIdentifiable))) { + continue; + } else { + try { + final boolean isEmbedded = isEmbedded(fieldValue); + if (iFetchPlan == null || (!(isEmbedded && iContext.fetchEmbeddedDocuments()) && !iFetchPlan.has(fieldPath, iCurrentLevel) + && depthLevel > -1 && iCurrentLevel >= depthLevel)) + // MAX DEPTH REACHED: STOP TO FETCH THIS FIELD + continue; + + final int nextLevel = isEmbedded ? iLevelFromRoot : iLevelFromRoot + 1; + + if (fieldValue instanceof ORecordId) + fieldValue = ((ORecordId) fieldValue).getRecord(); + + fetchRidMap(record, iFetchPlan, fieldValue, fieldName, iCurrentLevel, nextLevel, iFieldDepthLevel, parsedRecords, + fieldPath, iContext); + } catch (Exception e) { + OLogManager.instance().error(null, "Fetching error on record %s", e, record.getIdentity()); + } + } + } + } + + private static void fetchRidMap(final ODocument iRootRecord, final OFetchPlan iFetchPlan, final Object fieldValue, + final String fieldName, final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, + final Map parsedRecords, final String iFieldPathFromRoot, final OFetchContext iContext) throws IOException { + if (fieldValue == null) { + return; + } else if (fieldValue instanceof ODocument) { + fetchDocumentRidMap(iFetchPlan, fieldValue, fieldName, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iContext); + } else if (fieldValue instanceof Iterable) { + fetchCollectionRidMap(iFetchPlan, fieldValue, fieldName, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iContext); + } else if (fieldValue.getClass().isArray()) { + fetchArrayRidMap(iFetchPlan, fieldValue, fieldName, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iContext); + } else if (fieldValue instanceof Map) { + fetchMapRidMap(iFetchPlan, fieldValue, fieldName, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iContext); + } + } + + private static void fetchDocumentRidMap(final OFetchPlan iFetchPlan, Object fieldValue, String fieldName, final int iCurrentLevel, + final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchContext iContext) throws IOException { + updateRidMap(iFetchPlan, (ODocument) fieldValue, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iContext); + } + + @SuppressWarnings("unchecked") + private static void fetchCollectionRidMap(final OFetchPlan iFetchPlan, final Object fieldValue, final String fieldName, + final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, + final String iFieldPathFromRoot, final OFetchContext iContext) throws IOException { + final Iterable linked = (Iterable) fieldValue; + for (OIdentifiable d : linked) { + if (d != null) { + // GO RECURSIVELY + d = d.getRecord(); + + updateRidMap(iFetchPlan, (ODocument) d, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, iFieldPathFromRoot, + iContext); + } + } + } + + private static void fetchArrayRidMap(final OFetchPlan iFetchPlan, final Object fieldValue, final String fieldName, + final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, + final String iFieldPathFromRoot, final OFetchContext iContext) throws IOException { + if (fieldValue instanceof ODocument[]) { + final ODocument[] linked = (ODocument[]) fieldValue; + for (ODocument d : linked) + // GO RECURSIVELY + updateRidMap(iFetchPlan, (ODocument) d, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, iFieldPathFromRoot, + iContext); + } + } + + @SuppressWarnings("unchecked") + private static void fetchMapRidMap(final OFetchPlan iFetchPlan, Object fieldValue, String fieldName, final int iCurrentLevel, + final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchContext iContext) throws IOException { + final Map linked = (Map) fieldValue; + for (ODocument d : (linked).values()) + // GO RECURSIVELY + updateRidMap(iFetchPlan, (ODocument) d, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, iFieldPathFromRoot, + iContext); + } + + private static void updateRidMap(final OFetchPlan iFetchPlan, final ODocument fieldValue, final int iCurrentLevel, + final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchContext iContext) throws IOException { + if (fieldValue == null) + return; + + final Integer fetchedLevel = parsedRecords.get(fieldValue.getIdentity()); + int currentLevel = iCurrentLevel + 1; + int fieldDepthLevel = iFieldDepthLevel; + if (iFetchPlan != null && iFetchPlan.has(iFieldPathFromRoot, iCurrentLevel)) { + currentLevel = 1; + fieldDepthLevel = iFetchPlan.getDepthLevel(iFieldPathFromRoot, iCurrentLevel); + } + + final boolean isEmbedded = isEmbedded(fieldValue); + + if (isEmbedded || fetchedLevel == null) { + if (!isEmbedded) + parsedRecords.put(fieldValue.getIdentity(), iLevelFromRoot); + + processRecordRidMap(fieldValue, iFetchPlan, currentLevel, iLevelFromRoot, fieldDepthLevel, parsedRecords, iFieldPathFromRoot, + iContext); + } + } + + private static void processRecord(final ODocument record, final Object iUserObject, final OFetchPlan iFetchPlan, + final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, final Map parsedRecords, + final String iFieldPathFromRoot, final OFetchListener iListener, final OFetchContext iContext, final String iFormat) + throws IOException { + + if (record == null) + return; + + if (!iListener.requireFieldProcessing() && iFetchPlan == OFetchHelper.DEFAULT_FETCHPLAN) + return; + + Object fieldValue; + + iContext.onBeforeFetch(record); + Set toRemove = new HashSet(); + + for (String fieldName : record.fieldNames()) { + String fieldPath = !iFieldPathFromRoot.isEmpty() ? iFieldPathFromRoot + "." + fieldName : fieldName; + int depthLevel; + depthLevel = getDepthLevel(iFetchPlan, fieldPath, iCurrentLevel); + if (depthLevel == -2) { + toRemove.add(fieldName); + continue; + } + if (iFieldDepthLevel > -1) + depthLevel = iFieldDepthLevel; + + fieldValue = record.rawField(fieldName); + OType fieldType = record.fieldType(fieldName); + + boolean fetch = !iFormat.contains("shallow") && (!(fieldValue instanceof OIdentifiable) || depthLevel == -1 + || iCurrentLevel <= depthLevel || (iFetchPlan != null && iFetchPlan.has(fieldPath, iCurrentLevel))); + + final boolean isEmbedded = isEmbedded(fieldValue); + + if (!fetch && isEmbedded && iContext.fetchEmbeddedDocuments()) + // EMBEDDED, GO DEEPER + fetch = true; + + if (iFormat.contains("shallow") || fieldValue == null || (!fetch && fieldValue instanceof OIdentifiable) + || !(fieldValue instanceof OIdentifiable) + && (!(fieldValue instanceof ORecordLazyMultiValue) || !((ORecordLazyMultiValue) fieldValue).rawIterator().hasNext() + || !(((ORecordLazyMultiValue) fieldValue).rawIterator().next() instanceof OIdentifiable)) + && (!(fieldValue.getClass().isArray()) || Array.getLength(fieldValue) == 0 + || !(Array.get(fieldValue, 0) instanceof OIdentifiable)) + && !containsIdentifiers(fieldValue)) { + iContext.onBeforeStandardField(fieldValue, fieldName, iUserObject, fieldType); + iListener.processStandardField(record, fieldValue, fieldName, iContext, iUserObject, iFormat, fieldType); + iContext.onAfterStandardField(fieldValue, fieldName, iUserObject, fieldType); + } else { + try { + if (fetch) { + final int nextLevel = isEmbedded ? iLevelFromRoot : iLevelFromRoot + 1; + + fetch(record, iUserObject, iFetchPlan, fieldValue, fieldName, iCurrentLevel, nextLevel, iFieldDepthLevel, parsedRecords, + depthLevel, fieldPath, iListener, iContext); + } + + } catch (Exception e) { + OLogManager.instance().error(null, "Fetching error on record %s", e, record.getIdentity()); + } + } + } + for (String fieldName : toRemove) { + iListener.skipStandardField(record, fieldName, iContext, iUserObject, iFormat); + } + iContext.onAfterFetch(record); + } + + private static boolean containsIdentifiers(Object fieldValue) { + if (!OMultiValue.isMultiValue(fieldValue)) { + return false; + } + for (Object item : OMultiValue.getMultiValueIterable(fieldValue)) { + if (item instanceof OIdentifiable) { + return true; + } + if (containsIdentifiers(item)) { + return true; + } + } + return false; + } + + public static boolean isEmbedded(Object fieldValue) { + boolean isEmbedded = fieldValue instanceof ODocument + && (((ODocument) fieldValue).isEmbedded() || !((ODocument) fieldValue).getIdentity().isPersistent()); + + // ridbag can contain only edges no embedded documents are allowed. + if (fieldValue instanceof ORidBag) + return false; + if (!isEmbedded) { + try { + final Object f = OMultiValue.getFirstValue(fieldValue); + isEmbedded = f != null + && (f instanceof ODocument && (((ODocument) f).isEmbedded() || !((ODocument) f).getIdentity().isPersistent())); + } catch (Exception e) { + // IGNORE IT + } + } + return isEmbedded; + } + + private static void fetch(final ODocument iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + final Object fieldValue, final String fieldName, final int iCurrentLevel, final int iLevelFromRoot, + final int iFieldDepthLevel, final Map parsedRecords, final int depthLevel, final String iFieldPathFromRoot, + final OFetchListener iListener, final OFetchContext iContext) throws IOException { + + int currentLevel = iCurrentLevel + 1; + int fieldDepthLevel = iFieldDepthLevel; + if (iFetchPlan != null && iFetchPlan.has(iFieldPathFromRoot, iCurrentLevel)) { + currentLevel = 0; + fieldDepthLevel = iFetchPlan.getDepthLevel(iFieldPathFromRoot, iCurrentLevel); + } + + if (fieldValue == null) { + iListener.processStandardField(iRootRecord, null, fieldName, iContext, iUserObject, "", null); + } else if (fieldValue instanceof OIdentifiable) { + fetchDocument(iRootRecord, iUserObject, iFetchPlan, (OIdentifiable) fieldValue, fieldName, currentLevel, iLevelFromRoot, + fieldDepthLevel, parsedRecords, iFieldPathFromRoot, iListener, iContext); + + } else if (fieldValue instanceof Map) { + fetchMap(iRootRecord, iUserObject, iFetchPlan, fieldValue, fieldName, currentLevel, iLevelFromRoot, fieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else if (OMultiValue.isMultiValue(fieldValue)) { + fetchCollection(iRootRecord, iUserObject, iFetchPlan, fieldValue, fieldName, currentLevel, iLevelFromRoot, fieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else if (fieldValue.getClass().isArray()) { + fetchArray(iRootRecord, iUserObject, iFetchPlan, fieldValue, fieldName, currentLevel, iLevelFromRoot, fieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } + } + + @SuppressWarnings("unchecked") + private static void fetchMap(final ODocument iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + Object fieldValue, String fieldName, final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, + final Map parsedRecords, final String iFieldPathFromRoot, final OFetchListener iListener, + final OFetchContext iContext) throws IOException { + final Map linked = (Map) fieldValue; + iContext.onBeforeMap(iRootRecord, fieldName, iUserObject); + + for (Object key : linked.keySet()) { + final Object o = linked.get(key); + + if (o instanceof OIdentifiable) { + ORecord r = null; + try { + r = ((OIdentifiable) o).getRecord(); + } catch (ORecordNotFoundException notFound) { + } + if (r != null) { + if (r instanceof ODocument) { + // GO RECURSIVELY + final ODocument d = (ODocument) r; + final Integer fieldDepthLevel = parsedRecords.get(d.getIdentity()); + if (!d.getIdentity().isValid() || (fieldDepthLevel != null && fieldDepthLevel.intValue() == iLevelFromRoot)) { + removeParsedFromMap(parsedRecords, d); + iContext.onBeforeDocument(iRootRecord, d, key.toString(), iUserObject); + final Object userObject = iListener.fetchLinkedMapEntry(iRootRecord, iUserObject, fieldName, key.toString(), d, + iContext); + processRecord(d, userObject, iFetchPlan, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iListener, iContext, ""); + iContext.onAfterDocument(iRootRecord, d, key.toString(), iUserObject); + } else { + iListener.parseLinked(iRootRecord, d, iUserObject, key.toString(), iContext); + } + } else + iListener.parseLinked(iRootRecord, r, iUserObject, key.toString(), iContext); + + }else { + iListener.processStandardField(iRootRecord, o, key.toString(), iContext, iUserObject, "", null); + } + } else if (o instanceof Map) { + fetchMap(iRootRecord, iUserObject, iFetchPlan, o, key.toString(), iCurrentLevel + 1, iLevelFromRoot, iFieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else if (OMultiValue.isMultiValue(o)) { + fetchCollection(iRootRecord, iUserObject, iFetchPlan, o, key.toString(), iCurrentLevel + 1, iLevelFromRoot, + iFieldDepthLevel, parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else + iListener.processStandardField(iRootRecord, o, key.toString(), iContext, iUserObject, "", null); + } + iContext.onAfterMap(iRootRecord, fieldName, iUserObject); + } + + private static void fetchArray(final ODocument iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + Object fieldValue, String fieldName, final int iCurrentLevel, final int iLevelFromRoot, final int iFieldDepthLevel, + final Map parsedRecords, final String iFieldPathFromRoot, final OFetchListener iListener, + final OFetchContext iContext) throws IOException { + if (fieldValue instanceof ODocument[]) { + final ODocument[] linked = (ODocument[]) fieldValue; + iContext.onBeforeArray(iRootRecord, fieldName, iUserObject, linked); + for (ODocument d : linked) { + // GO RECURSIVELY + final Integer fieldDepthLevel = parsedRecords.get(d.getIdentity()); + if (!d.getIdentity().isValid() || (fieldDepthLevel != null && fieldDepthLevel.intValue() == iLevelFromRoot)) { + removeParsedFromMap(parsedRecords, d); + iContext.onBeforeDocument(iRootRecord, d, fieldName, iUserObject); + final Object userObject = iListener.fetchLinked(iRootRecord, iUserObject, fieldName, d, iContext); + processRecord(d, userObject, iFetchPlan, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iListener, iContext, ""); + iContext.onAfterDocument(iRootRecord, d, fieldName, iUserObject); + } else { + iListener.parseLinkedCollectionValue(iRootRecord, d, iUserObject, fieldName, iContext); + } + } + iContext.onAfterArray(iRootRecord, fieldName, iUserObject); + } else { + iListener.processStandardField(iRootRecord, fieldValue, fieldName, iContext, iUserObject, "", null); + } + } + + @SuppressWarnings("unchecked") + private static void fetchCollection(final ODocument iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + final Object fieldValue, final String fieldName, final int iCurrentLevel, final int iLevelFromRoot, + final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchListener iListener, final OFetchContext iContext) throws IOException { + final Iterable linked; + if (fieldValue instanceof Iterable || fieldValue instanceof ORidBag) { + linked = (Iterable) fieldValue; + iContext.onBeforeCollection(iRootRecord, fieldName, iUserObject, (Iterable) linked); + } else if (fieldValue.getClass().isArray()) { + linked = OMultiValue.getMultiValueIterable(fieldValue, false); + iContext.onBeforeCollection(iRootRecord, fieldName, iUserObject, (Iterable) linked); + } else if (fieldValue instanceof Map) { + linked = (Collection) ((Map) fieldValue).values(); + iContext.onBeforeMap(iRootRecord, fieldName, iUserObject); + } else + throw new IllegalStateException("Unrecognized type: " + fieldValue.getClass()); + + final Iterator iter; + if (linked instanceof ORecordLazyMultiValue) + iter = ((ORecordLazyMultiValue) linked).rawIterator(); + else + iter = linked.iterator(); + + try { + while (iter.hasNext()) { + final Object o = iter.next(); + if (o == null) + continue; + + if (o instanceof OIdentifiable) { + OIdentifiable d = (OIdentifiable) o; + + // GO RECURSIVELY + final Integer fieldDepthLevel = parsedRecords.get(d.getIdentity()); + if (!d.getIdentity().isPersistent() || (fieldDepthLevel != null && fieldDepthLevel.intValue() == iLevelFromRoot)) { + removeParsedFromMap(parsedRecords, d); + d = d.getRecord(); + + if (d == null) + iListener.processStandardField(null, d, null, iContext, iUserObject, "", null); + else if (!(d instanceof ODocument)) { + iListener.processStandardField(null, d, fieldName, iContext, iUserObject, "", null); + } else { + iContext.onBeforeDocument(iRootRecord, (ODocument) d, fieldName, iUserObject); + final Object userObject = iListener.fetchLinkedCollectionValue(iRootRecord, iUserObject, fieldName, (ODocument) d, + iContext); + processRecord((ODocument) d, userObject, iFetchPlan, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iListener, iContext, ""); + iContext.onAfterDocument(iRootRecord, (ODocument) d, fieldName, iUserObject); + } + } else { + iListener.parseLinkedCollectionValue(iRootRecord, d, iUserObject, fieldName, iContext); + } + } else if (o instanceof Map) { + fetchMap(iRootRecord, iUserObject, iFetchPlan, o, null, iCurrentLevel + 1, iLevelFromRoot, iFieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else if (OMultiValue.isMultiValue(o)) { + fetchCollection(iRootRecord, iUserObject, iFetchPlan, o, null, iCurrentLevel + 1, iLevelFromRoot, iFieldDepthLevel, + parsedRecords, iFieldPathFromRoot, iListener, iContext); + } else if (o instanceof String || o instanceof Number || o instanceof Boolean) { + ((OJSONFetchContext) iContext).getJsonWriter().writeValue(0, false, o); + } + } + } finally { + if (fieldValue instanceof Iterable || fieldValue instanceof ORidBag) + iContext.onAfterCollection(iRootRecord, fieldName, iUserObject); + else if (fieldValue.getClass().isArray()) + iContext.onAfterCollection(iRootRecord, fieldName, iUserObject); + else if (fieldValue instanceof Map) + iContext.onAfterMap(iRootRecord, fieldName, iUserObject); + } + } + + private static void fetchDocument(final ODocument iRootRecord, final Object iUserObject, final OFetchPlan iFetchPlan, + final OIdentifiable fieldValue, final String fieldName, final int iCurrentLevel, final int iLevelFromRoot, + final int iFieldDepthLevel, final Map parsedRecords, final String iFieldPathFromRoot, + final OFetchListener iListener, final OFetchContext iContext) throws IOException { + if (fieldValue instanceof ORID && !((ORID) fieldValue).isValid()) { + // RID NULL: TREAT AS "NULL" VALUE + iContext.onBeforeStandardField(fieldValue, fieldName, iRootRecord, null); + iListener.parseLinked(iRootRecord, fieldValue, iUserObject, fieldName, iContext); + iContext.onAfterStandardField(fieldValue, fieldName, iRootRecord, null); + return; + } + + final Integer fieldDepthLevel = parsedRecords.get(fieldValue.getIdentity()); + if (!fieldValue.getIdentity().isValid() || (fieldDepthLevel != null && fieldDepthLevel.intValue() == iLevelFromRoot)) { + removeParsedFromMap(parsedRecords, fieldValue); + final ODocument linked = (ODocument) fieldValue.getRecord(); + if (linked == null) + return; + + iContext.onBeforeDocument(iRootRecord, linked, fieldName, iUserObject); + Object userObject = iListener.fetchLinked(iRootRecord, iUserObject, fieldName, linked, iContext); + processRecord(linked, userObject, iFetchPlan, iCurrentLevel, iLevelFromRoot, iFieldDepthLevel, parsedRecords, + iFieldPathFromRoot, iListener, iContext, ""); + iContext.onAfterDocument(iRootRecord, linked, fieldName, iUserObject); + } else { + iContext.onBeforeStandardField(fieldValue, fieldName, iRootRecord, null); + iListener.parseLinked(iRootRecord, fieldValue, iUserObject, fieldName, iContext); + iContext.onAfterStandardField(fieldValue, fieldName, iRootRecord, null); + } + } + + protected static void removeParsedFromMap(final Map parsedRecords, OIdentifiable d) { + parsedRecords.remove(d.getIdentity()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchListener.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchListener.java new file mode 100644 index 00000000000..6f2816e9872 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchListener.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.fetch; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Listener interface used while fetching records. + * + * @author Luca Garulli + * + */ +public interface OFetchListener { + /** + * Returns true if the listener fetches he fields. + */ + boolean requireFieldProcessing(); + + /** + * Fetch the linked field. + * + * @param iRoot + * @param iFieldName + * @param iLinked + * @return null if the fetching must stop, otherwise the current field value + */ + Object fetchLinked(final ODocument iRoot, final Object iUserObject, final String iFieldName, final ODocument iLinked, + final OFetchContext iContext) throws OFetchException; + + void parseLinked(final ODocument iRootRecord, final OIdentifiable iLinked, final Object iUserObject, final String iFieldName, + final OFetchContext iContext) throws OFetchException; + + void parseLinkedCollectionValue(final ODocument iRootRecord, final OIdentifiable iLinked, final Object iUserObject, + final String iFieldName, final OFetchContext iContext) throws OFetchException; + + Object fetchLinkedMapEntry(final ODocument iRoot, final Object iUserObject, final String iFieldName, final String iKey, + final ODocument iLinked, final OFetchContext iContext) throws OFetchException; + + Object fetchLinkedCollectionValue(final ODocument iRoot, final Object iUserObject, final String iFieldName, + final ODocument iLinked, final OFetchContext iContext) throws OFetchException; + + void processStandardField(final ODocument iRecord, final Object iFieldValue, final String iFieldName, + final OFetchContext iContext, final Object iUserObject, String iFormat, OType filedType) throws OFetchException; + + void skipStandardField(final ODocument iRecord, final String iFieldName, final OFetchContext iContext, final Object iUserObject, + String iFormat) throws OFetchException; + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchPlan.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchPlan.java new file mode 100644 index 00000000000..b043759b7d3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/OFetchPlan.java @@ -0,0 +1,224 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.fetch; + +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OFetchPlan { + static final String ANY_WILDCARD = "*"; + + final Map fetchPlan = new HashMap(); + final Map fetchPlanStartsWith = new HashMap(); + + private static class OFetchPlanLevel { + public int depthLevelFrom; + public int depthLevelTo; + public int level; + + public OFetchPlanLevel(final int iFrom, final int iTo, final int iLevel) { + depthLevelFrom = iFrom; + depthLevelTo = iTo; + level = iLevel; + } + } + + public OFetchPlan(final String iFetchPlan) { + fetchPlan.put(ANY_WILDCARD, new OFetchPlanLevel(0, 0, 0)); + + if (iFetchPlan != null && !iFetchPlan.isEmpty()) { + // CHECK IF THERE IS SOME FETCH-DEPTH + final List planParts = OStringSerializerHelper.split(iFetchPlan, ' '); + if (!planParts.isEmpty()) { + for (String planPart : planParts) { + final List parts = OStringSerializerHelper.split(planPart, ':'); + if (parts.size() != 2) { + throw new IllegalArgumentException("Wrong fetch plan: " + planPart); + } + + String key = parts.get(0); + final int level = Integer.parseInt(parts.get(1)); + + final OFetchPlanLevel fp; + + if (key.startsWith("[")) { + // EXTRACT DEPTH LEVEL + final int endLevel = key.indexOf("]"); + if (endLevel == -1) + throw new IllegalArgumentException("Missing closing square bracket on depth level in fetch plan: " + key); + + final String range = key.substring(1, endLevel); + key = key.substring(endLevel + 1); + + if (key.indexOf(".") > -1) + throw new IllegalArgumentException( + "Nested levels (fields separated by dot) are not allowed on fetch plan when dynamic depth level is specified (square brackets): " + + key); + + final List indexRanges = OStringSerializerHelper.smartSplit(range, '-', ' '); + if (indexRanges.size() > 1) { + // MULTI VALUES RANGE + String from = indexRanges.get(0); + String to = indexRanges.get(1); + + final int rangeFrom = from != null && !from.isEmpty() ? Integer.parseInt(from) : 0; + final int rangeTo = to != null && !to.isEmpty() ? Integer.parseInt(to) : -1; + + fp = new OFetchPlanLevel(rangeFrom, rangeTo, level); + } else if (range.equals("*")) + // CREATE FETCH PLAN WITH INFINITE DEPTH + fp = new OFetchPlanLevel(0, -1, level); + else { + // CREATE FETCH PLAN WITH ONE LEVEL ONLY OF DEPTH + final int v = Integer.parseInt(range); + fp = new OFetchPlanLevel(v, v, level); + } + } else { + if (level == -1) + // CREATE FETCH PLAN FOR INFINITE LEVEL + fp = new OFetchPlanLevel(0, -1, level); + else + // CREATE FETCH PLAN FOR FIRST LEVEL ONLY + fp = new OFetchPlanLevel(0, 0, level); + } + + if (key.length() > 1 && key.endsWith(ANY_WILDCARD)) { + fetchPlanStartsWith.put(key.substring(0, key.length() - 1), fp); + } else { + fetchPlan.put(key, fp); + } + } + } + } + } + + public int getDepthLevel(final String iFieldPath, final int iCurrentLevel) { + final OFetchPlanLevel value = fetchPlan.get(ANY_WILDCARD); + final Integer defDepthLevel = value.level; + + final String[] fpParts = iFieldPath.split("\\."); + + for (Map.Entry fpLevel : fetchPlan.entrySet()) { + final String fpLevelKey = fpLevel.getKey(); + final OFetchPlanLevel fpLevelValue = fpLevel.getValue(); + + if (iCurrentLevel >= fpLevelValue.depthLevelFrom + && (fpLevelValue.depthLevelTo == -1 || iCurrentLevel <= fpLevelValue.depthLevelTo)) { + // IT'S IN RANGE + if (iFieldPath.equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return fpLevelValue.level; + else if (fpLevelKey.startsWith(iFieldPath)) + // SETS THE FETCH LEVEL TO 1 (LOADS ALL DOCUMENT FIELDS) + return 1; + + for (int i = 0; i < fpParts.length; ++i) { + if (i >= fpLevelValue.depthLevelFrom && (fpLevelValue.depthLevelTo == -1 || i <= fpLevelValue.depthLevelTo)) { + // IT'S IN RANGE + if (fpParts[i].equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return fpLevelValue.level; + } + } + } else { + if (iFieldPath.equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return fpLevelValue.level; + else if (fpLevelKey.startsWith(iFieldPath)) + // SETS THE FETCH LEVEL TO 1 (LOADS ALL DOCUMENT FIELDS) + return 1; + } + } + + if (!fetchPlanStartsWith.isEmpty()) { + for (Map.Entry fpLevel : fetchPlanStartsWith.entrySet()) { + final String fpLevelKey = fpLevel.getKey(); + final OFetchPlanLevel fpLevelValue = fpLevel.getValue(); + + if (iCurrentLevel >= fpLevelValue.depthLevelFrom + && (fpLevelValue.depthLevelTo == -1 || iCurrentLevel <= fpLevelValue.depthLevelTo)) { + // IT'S IN RANGE + for (int i = 0; i < fpParts.length; ++i) { + if (fpParts[i].startsWith(fpLevelKey)) + return fpLevelValue.level; + } + } + } + } + + return defDepthLevel.intValue(); + } + + public boolean has(final String iFieldPath, final int iCurrentLevel) { + final String[] fpParts = iFieldPath.split("\\."); + + for (Map.Entry fpLevel : fetchPlan.entrySet()) { + final String fpLevelKey = fpLevel.getKey(); + final OFetchPlanLevel fpLevelValue = fpLevel.getValue(); + + if (iCurrentLevel >= fpLevelValue.depthLevelFrom + && (fpLevelValue.depthLevelTo == -1 || iCurrentLevel <= fpLevelValue.depthLevelTo)) { + if (iFieldPath.equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return true; + else if (fpLevelKey.startsWith(iFieldPath)) + // SETS THE FETCH LEVEL TO 1 (LOADS ALL DOCUMENT FIELDS) + return true; + + for (int i = 0; i < fpParts.length; ++i) { + if (i >= fpLevelValue.depthLevelFrom && (fpLevelValue.depthLevelTo == -1 || i <= fpLevelValue.depthLevelTo)) { + // IT'S IN RANGE + if (fpParts[i].equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return true; + } + } + } else { + if (iFieldPath.equals(fpLevelKey)) + // GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED + return true; + else if (fpLevelKey.startsWith(iFieldPath)) + // SETS THE FETCH LEVEL TO 1 (LOADS ALL DOCUMENT FIELDS) + return true; + } + } + + if (!fetchPlanStartsWith.isEmpty()) { + for (Map.Entry fpLevel : fetchPlanStartsWith.entrySet()) { + final String fpLevelKey = fpLevel.getKey(); + final OFetchPlanLevel fpLevelValue = fpLevel.getValue(); + + if (iCurrentLevel >= fpLevelValue.depthLevelFrom + && (fpLevelValue.depthLevelTo == -1 || iCurrentLevel <= fpLevelValue.depthLevelTo)) { + // IT'S IN RANGE + for (int i = 0; i < fpParts.length; ++i) { + if (fpParts[i].startsWith(fpLevelKey)) + return true; + } + } + } + } + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchContext.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchContext.java new file mode 100755 index 00000000000..971dece4335 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchContext.java @@ -0,0 +1,263 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.fetch.json; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.fetch.OFetchContext; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.OJSONWriter; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerJSON; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerJSON.FormatSettings; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.Set; +import java.util.Stack; + +/** + * @author luca.molino + * + */ +public class OJSONFetchContext implements OFetchContext { + + protected final OJSONWriter jsonWriter; + protected final FormatSettings settings; + protected final Stack typesStack = new Stack(); + protected final Stack collectionStack = new Stack(); + + public OJSONFetchContext(final OJSONWriter iJsonWriter, final FormatSettings iSettings) { + jsonWriter = iJsonWriter; + settings = iSettings; + } + + public void onBeforeFetch(final ODocument iRootRecord) { + typesStack.add(new StringBuilder()); + } + + public void onAfterFetch(final ODocument iRootRecord) { + StringBuilder buffer = typesStack.pop(); + if (settings.keepTypes && buffer.length() > 0) + try { + jsonWriter.writeAttribute(settings.indentLevel > -1 ? settings.indentLevel : 1, true, + ORecordSerializerJSON.ATTRIBUTE_FIELD_TYPES, buffer.toString()); + } catch (IOException e) { + throw OException.wrapException(new OFetchException("Error writing field types"), e); + } + } + + public void onBeforeStandardField(final Object iFieldValue, final String iFieldName, final Object iUserObject, OType fieldType) { + manageTypes(iFieldName, iFieldValue, fieldType); + } + + public void onAfterStandardField(Object iFieldValue, String iFieldName, Object iUserObject, OType fieldType) { + } + + public void onBeforeArray(final ODocument iRootRecord, final String iFieldName, final Object iUserObject, + final OIdentifiable[] iArray) { + onBeforeCollection(iRootRecord, iFieldName, iUserObject, null); + } + + public void onAfterArray(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) { + onAfterCollection(iRootRecord, iFieldName, iUserObject); + } + + public void onBeforeCollection(final ODocument iRootRecord, final String iFieldName, final Object iUserObject, + final Iterable iterable) { + try { + manageTypes(iFieldName, iterable, null); + jsonWriter.beginCollection(++settings.indentLevel, true, iFieldName); + collectionStack.add(iRootRecord); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing collection field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void onAfterCollection(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) { + try { + jsonWriter.endCollection(settings.indentLevel--, true); + collectionStack.pop(); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing collection field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void onBeforeMap(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) { + try { + jsonWriter.beginObject(++settings.indentLevel, true, iFieldName); + if (!(iUserObject instanceof ODocument)) { + collectionStack.add(new ODocument()); // <-- sorry for this... fixes #2845 but this mess should be rewritten... + } + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing map field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void onAfterMap(final ODocument iRootRecord, final String iFieldName, final Object iUserObject) { + try { + jsonWriter.endObject(--settings.indentLevel, true); + if (!(iUserObject instanceof ODocument)) { + collectionStack.pop(); + } + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing map field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void onBeforeDocument(final ODocument iRootRecord, final ODocument iDocument, final String iFieldName, + final Object iUserObject) { + try { + final String fieldName; + if (!collectionStack.isEmpty() && collectionStack.peek().equals(iRootRecord)) + fieldName = null; + else + fieldName = iFieldName; + jsonWriter.beginObject(++settings.indentLevel, true, fieldName); + writeSignature(jsonWriter, iDocument); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing link field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void onAfterDocument(final ODocument iRootRecord, final ODocument iDocument, final String iFieldName, + final Object iUserObject) { + try { + jsonWriter.endObject(settings.indentLevel--, true); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing link field " + iFieldName + " of record " + iRootRecord.getIdentity()), e); + } + } + + public void writeLinkedValue(final OIdentifiable iRecord, final String iFieldName) throws IOException { + jsonWriter.writeValue(settings.indentLevel, true, OJSONWriter.encode(iRecord.getIdentity())); + } + + public void writeLinkedAttribute(final OIdentifiable iRecord, final String iFieldName) throws IOException { + final Object link = iRecord.getIdentity().isValid() ? OJSONWriter.encode(iRecord.getIdentity()) : null; + jsonWriter.writeAttribute(settings.indentLevel, true, iFieldName, link); + } + + public boolean isInCollection(ODocument record) { + return !collectionStack.isEmpty() && collectionStack.peek().equals(record); + } + + public OJSONWriter getJsonWriter() { + return jsonWriter; + } + + public int getIndentLevel() { + return settings.indentLevel; + } + + public void writeSignature(final OJSONWriter json, final ORecord record) throws IOException { + if (record == null) { + json.write("null"); + return; + } + + boolean firstAttribute = true; + + if (settings.includeType) { + json.writeAttribute(firstAttribute ? settings.indentLevel : 1, firstAttribute, ODocumentHelper.ATTRIBUTE_TYPE, + "" + (char) ORecordInternal.getRecordType(record)); + if (settings.attribSameRow) + firstAttribute = false; + } + if (settings.includeId && record.getIdentity() != null && record.getIdentity().isValid()) { + json.writeAttribute(!firstAttribute ? settings.indentLevel : 1, firstAttribute, ODocumentHelper.ATTRIBUTE_RID, + record.getIdentity().toString()); + if (settings.attribSameRow) + firstAttribute = false; + } + if (settings.includeVer) { + json.writeAttribute(firstAttribute ? settings.indentLevel : 1, firstAttribute, ODocumentHelper.ATTRIBUTE_VERSION, + record.getVersion()); + if (settings.attribSameRow) + firstAttribute = false; + } + if (settings.includeClazz && record instanceof ODocument && ((ODocument) record).getClassName() != null) { + json.writeAttribute(firstAttribute ? settings.indentLevel : 1, firstAttribute, ODocumentHelper.ATTRIBUTE_CLASS, + ((ODocument) record).getClassName()); + if (settings.attribSameRow) + firstAttribute = false; + } + } + + public boolean fetchEmbeddedDocuments() { + return settings.alwaysFetchEmbeddedDocuments; + } + + protected void manageTypes(final String iFieldName, final Object iFieldValue, OType fieldType) { + if (settings.keepTypes) { + if (iFieldValue instanceof Long) + appendType(typesStack.peek(), iFieldName, 'l'); + else if (iFieldValue instanceof OIdentifiable) + appendType(typesStack.peek(), iFieldName, 'x'); + else if (iFieldValue instanceof Float) + appendType(typesStack.peek(), iFieldName, 'f'); + else if (iFieldValue instanceof Short) + appendType(typesStack.peek(), iFieldName, 's'); + else if (iFieldValue instanceof Double) + appendType(typesStack.peek(), iFieldName, 'd'); + else if (iFieldValue instanceof Date) + appendType(typesStack.peek(), iFieldName, 't'); + else if (iFieldValue instanceof Byte || iFieldValue instanceof byte[]) + appendType(typesStack.peek(), iFieldName, 'b'); + else if (iFieldValue instanceof BigDecimal) + appendType(typesStack.peek(), iFieldName, 'c'); + else if (iFieldValue instanceof ORecordLazySet) + appendType(typesStack.peek(), iFieldName, 'n'); + else if (iFieldValue instanceof Set) + appendType(typesStack.peek(), iFieldName, 'e'); + else if (iFieldValue instanceof ORidBag) + appendType(typesStack.peek(), iFieldName, 'g'); + else { + OType t = fieldType; + if (t == null) + t = OType.getTypeByValue(iFieldValue); + if (t == OType.LINKLIST) + appendType(typesStack.peek(), iFieldName, 'z'); + else if (t == OType.LINKMAP) + appendType(typesStack.peek(), iFieldName, 'm'); + else if (t == OType.CUSTOM) + appendType(typesStack.peek(), iFieldName, 'u'); + } + } + } + + private void appendType(final StringBuilder iBuffer, final String iFieldName, final char iType) { + if (iBuffer.length() > 0) + iBuffer.append(','); + iBuffer.append(iFieldName); + iBuffer.append('='); + iBuffer.append(iType); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchListener.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchListener.java new file mode 100755 index 00000000000..e6852537d43 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/json/OJSONFetchListener.java @@ -0,0 +1,105 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.fetch.json; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.fetch.OFetchContext; +import com.orientechnologies.orient.core.fetch.OFetchListener; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OJSONWriter; + +import java.io.IOException; + +/** + * @author luca.molino + * + */ +public class OJSONFetchListener implements OFetchListener { + + public boolean requireFieldProcessing() { + return true; + } + + public void processStandardField(final ODocument iRecord, final Object iFieldValue, final String iFieldName, final OFetchContext iContext, final Object iusObject, final String iFormat, + OType filedType) { + try { + ((OJSONFetchContext) iContext).getJsonWriter().writeAttribute(((OJSONFetchContext) iContext).getIndentLevel() + 1, true, iFieldName, iFieldValue,iFormat,filedType); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error processing field '" + iFieldValue + " of record " + iRecord.getIdentity()), e); + } + } + + public void processStandardCollectionValue(final Object iFieldValue, final OFetchContext iContext) throws OFetchException { + try { + ((OJSONFetchContext) iContext).getJsonWriter().writeValue(((OJSONFetchContext) iContext).getIndentLevel(), true, + OJSONWriter.encode(iFieldValue)); + } catch (IOException e) { + OLogManager.instance().error(this, "Error on processStandardCollectionValue", e); + } + } + + public Object fetchLinked(final ODocument iRecord, final Object iUserObject, final String iFieldName, final ODocument iLinked, + final OFetchContext iContext) throws OFetchException { + return iLinked; + } + + public Object fetchLinkedMapEntry(final ODocument iRecord, final Object iUserObject, final String iFieldName, final String iKey, + final ODocument iLinked, final OFetchContext iContext) throws OFetchException { + return iLinked; + } + + public void parseLinked(final ODocument iRootRecord, final OIdentifiable iLinked, final Object iUserObject, + final String iFieldName, final OFetchContext iContext) throws OFetchException { + try { + ((OJSONFetchContext) iContext).writeLinkedAttribute(iLinked, iFieldName); + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing linked field " + iFieldName + " (record:" + iLinked.getIdentity() + ") of record " + + iRootRecord.getIdentity()), e); + } + } + + public void parseLinkedCollectionValue(ODocument iRootRecord, OIdentifiable iLinked, Object iUserObject, String iFieldName, + OFetchContext iContext) throws OFetchException { + try { + if (((OJSONFetchContext) iContext).isInCollection(iRootRecord)) { + ((OJSONFetchContext) iContext).writeLinkedValue(iLinked, iFieldName); + } else { + ((OJSONFetchContext) iContext).writeLinkedAttribute(iLinked, iFieldName); + } + } catch (IOException e) { + throw OException.wrapException( + new OFetchException("Error writing linked field " + iFieldName + " (record:" + iLinked.getIdentity() + ") of record " + + iRootRecord.getIdentity()), e); + } + } + + public Object fetchLinkedCollectionValue(ODocument iRoot, Object iUserObject, String iFieldName, ODocument iLinked, + OFetchContext iContext) throws OFetchException { + return iLinked; + } + + @Override + public void skipStandardField(ODocument iRecord, String iFieldName, OFetchContext iContext, Object iUserObject, String iFormat) + throws OFetchException { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchContext.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchContext.java new file mode 100755 index 00000000000..7470850f3fb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchContext.java @@ -0,0 +1,76 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.fetch.remote; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.fetch.OFetchContext; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Fetch context for {@class ONetworkBinaryProtocol} class + * + * @author luca.molino + * + */ +public class ORemoteFetchContext implements OFetchContext { + public void onBeforeStandardField(Object iFieldValue, String iFieldName, Object iUserObject, OType fieldType) { + } + + public void onAfterStandardField(Object iFieldValue, String iFieldName, Object iUserObject, OType fieldType) { + } + + public void onBeforeMap(ODocument iRootRecord, String iFieldName, final Object iUserObject) throws OFetchException { + } + + public void onBeforeFetch(ODocument iRootRecord) throws OFetchException { + } + + public void onBeforeArray(ODocument iRootRecord, String iFieldName, Object iUserObject, OIdentifiable[] iArray) + throws OFetchException { + } + + public void onAfterArray(ODocument iRootRecord, String iFieldName, Object iUserObject) throws OFetchException { + } + + public void onBeforeDocument(ODocument iRecord, final ODocument iDocument, String iFieldName, + final Object iUserObject) throws OFetchException { + } + + public void onBeforeCollection(ODocument iRootRecord, String iFieldName, final Object iUserObject, + final Iterable iterable) throws OFetchException { + } + + public void onAfterMap(ODocument iRootRecord, String iFieldName, final Object iUserObject) throws OFetchException { + } + + public void onAfterFetch(ODocument iRootRecord) throws OFetchException { + } + + public void onAfterDocument(ODocument iRootRecord, final ODocument iDocument, String iFieldName, + final Object iUserObject) throws OFetchException { + } + + public void onAfterCollection(ODocument iRootRecord, String iFieldName, final Object iUserObject) + throws OFetchException { + } + + public boolean fetchEmbeddedDocuments() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchListener.java b/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchListener.java new file mode 100644 index 00000000000..297b3c6ac76 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/fetch/remote/ORemoteFetchListener.java @@ -0,0 +1,85 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.fetch.remote; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OFetchException; +import com.orientechnologies.orient.core.fetch.OFetchContext; +import com.orientechnologies.orient.core.fetch.OFetchListener; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Fetch listener for {@class ONetworkBinaryProtocol} class + * + * Whenever a record has to be fetched it will be added to the list of records to send + * + * @author luca.molino + * + */ +public abstract class ORemoteFetchListener implements OFetchListener { + public boolean requireFieldProcessing() { + return false; + } + + public ORemoteFetchListener() { + } + + protected abstract void sendRecord(ORecord iLinked); + + public void processStandardField(ODocument iRecord, Object iFieldValue, String iFieldName, OFetchContext iContext, + final Object iusObject, final String iFormat, OType filedType) throws OFetchException { + } + + public void parseLinked(ODocument iRootRecord, OIdentifiable iLinked, Object iUserObject, String iFieldName, + OFetchContext iContext) throws OFetchException { + } + + public void parseLinkedCollectionValue(ODocument iRootRecord, OIdentifiable iLinked, Object iUserObject, String iFieldName, + OFetchContext iContext) throws OFetchException { + } + + public Object fetchLinkedMapEntry(ODocument iRoot, Object iUserObject, String iFieldName, String iKey, ODocument iLinked, + OFetchContext iContext) throws OFetchException { + if (iLinked.getIdentity().isValid()) { + sendRecord(iLinked); + return true; + } + return null; + } + + public Object fetchLinkedCollectionValue(ODocument iRoot, Object iUserObject, String iFieldName, ODocument iLinked, + OFetchContext iContext) throws OFetchException { + if (iLinked.getIdentity().isValid()) { + sendRecord(iLinked); + return true; + } + return null; + } + + public Object fetchLinked(ODocument iRoot, Object iUserObject, String iFieldName, ODocument iLinked, OFetchContext iContext) + throws OFetchException { + sendRecord(iLinked); + return true; + } + + @Override + public void skipStandardField(ODocument iRecord, String iFieldName, OFetchContext iContext, Object iUserObject, String iFormat) + throws OFetchException { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/hook/ODocumentHookAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/hook/ODocumentHookAbstract.java new file mode 100755 index 00000000000..091470a19fb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/hook/ODocumentHookAbstract.java @@ -0,0 +1,343 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.hook; + +import com.orientechnologies.orient.core.db.ODatabase.STATUS; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Hook abstract class that calls separate methods for ODocument records. + * + * @author Luca Garulli + * @see ORecordHook + */ +public abstract class ODocumentHookAbstract implements ORecordHook { + private String[] includeClasses; + private String[] excludeClasses; + + protected ODatabaseDocument database; + + @Deprecated + public ODocumentHookAbstract() { + this.database = ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public ODocumentHookAbstract(ODatabaseDocument database) { + this.database = database; + } + + @Override + public void onUnregister() { + } + + /** + * It's called just before to create the new document. + * + * @param iDocument The document to create + * + * @return True if the document has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeCreate(final ODocument iDocument) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the document is created. + * + * @param iDocument The document is going to be created + */ + public void onRecordAfterCreate(final ODocument iDocument) { + } + + /** + * It's called just after the document creation was failed. + * + * @param iDocument The document just created + */ + public void onRecordCreateFailed(final ODocument iDocument) { + } + + /** + * It's called just after the document creation was replicated on another node. + * + * @param iDocument The document just created + */ + public void onRecordCreateReplicated(final ODocument iDocument) { + } + + /** + * It's called just before to read the document. + * + * @param iDocument The document to read + * + * @return True if the document has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeRead(final ODocument iDocument) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the document is read. + * + * @param iDocument The document just read + */ + public void onRecordAfterRead(final ODocument iDocument) { + } + + /** + * It's called just after the document read was failed. + * + * @param iDocument The document just created + */ + public void onRecordReadFailed(final ODocument iDocument) { + } + + /** + * It's called just after the document read was replicated on another node. + * + * @param iDocument The document just created + */ + public void onRecordReadReplicated(final ODocument iDocument) { + } + + /** + * It's called just before to update the document. + * + * @param iDocument The document to update + * + * @return True if the document has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the document is updated. + * + * @param iDocument The document just updated + */ + public void onRecordAfterUpdate(final ODocument iDocument) { + } + + /** + * It's called just after the document updated was failed. + * + * @param iDocument The document is going to be updated + */ + public void onRecordUpdateFailed(final ODocument iDocument) { + } + + /** + * It's called just after the document updated was replicated. + * + * @param iDocument The document is going to be updated + */ + public void onRecordUpdateReplicated(final ODocument iDocument) { + } + + /** + * It's called just before to delete the document. + * + * @param iDocument The document to delete + * + * @return True if the document has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeDelete(final ODocument iDocument) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the document is deleted. + * + * @param iDocument The document just deleted + */ + public void onRecordAfterDelete(final ODocument iDocument) { + } + + /** + * It's called just after the document deletion was failed. + * + * @param iDocument The document is going to be deleted + */ + public void onRecordDeleteFailed(final ODocument iDocument) { + } + + /** + * It's called just after the document deletion was replicated. + * + * @param iDocument The document is going to be deleted + */ + public void onRecordDeleteReplicated(final ODocument iDocument) { + } + + public void onRecordFinalizeUpdate(final ODocument document) { + } + + public void onRecordFinalizeCreation(final ODocument document) { + } + + public void onRecordFinalizeDeletion(final ODocument document) { + } + + public RESULT onTrigger(final TYPE iType, final ORecord iRecord) { + if (database.getStatus() != STATUS.OPEN) + return RESULT.RECORD_NOT_CHANGED; + + if (!(iRecord instanceof ODocument)) + return RESULT.RECORD_NOT_CHANGED; + + final ODocument document = (ODocument) iRecord; + + if (!filterBySchemaClass(document)) + return RESULT.RECORD_NOT_CHANGED; + + switch (iType) { + case BEFORE_CREATE: + return onRecordBeforeCreate(document); + + case AFTER_CREATE: + onRecordAfterCreate(document); + break; + + case CREATE_FAILED: + onRecordCreateFailed(document); + break; + + case CREATE_REPLICATED: + onRecordCreateReplicated(document); + break; + + case BEFORE_READ: + return onRecordBeforeRead(document); + + case AFTER_READ: + onRecordAfterRead(document); + break; + + case READ_FAILED: + onRecordReadFailed(document); + break; + + case READ_REPLICATED: + onRecordReadReplicated(document); + break; + + case BEFORE_UPDATE: + return onRecordBeforeUpdate(document); + + case AFTER_UPDATE: + onRecordAfterUpdate(document); + break; + + case UPDATE_FAILED: + onRecordUpdateFailed(document); + break; + + case UPDATE_REPLICATED: + onRecordUpdateReplicated(document); + break; + + case BEFORE_DELETE: + return onRecordBeforeDelete(document); + + case AFTER_DELETE: + onRecordAfterDelete(document); + break; + + case DELETE_FAILED: + onRecordDeleteFailed(document); + break; + + case DELETE_REPLICATED: + onRecordDeleteReplicated(document); + break; + + case FINALIZE_CREATION: + onRecordFinalizeCreation(document); + break; + + case FINALIZE_UPDATE: + onRecordFinalizeUpdate(document); + break; + + case FINALIZE_DELETION: + onRecordFinalizeDeletion(document); + break; + + default: + throw new IllegalStateException("Hook method " + iType + " is not managed"); + } + + return RESULT.RECORD_NOT_CHANGED; + } + + public String[] getIncludeClasses() { + return includeClasses; + } + + public ODocumentHookAbstract setIncludeClasses(final String... includeClasses) { + if (excludeClasses != null) + throw new IllegalStateException("Cannot include classes if exclude classes has been set"); + this.includeClasses = includeClasses; + return this; + } + + public String[] getExcludeClasses() { + return excludeClasses; + } + + public ODocumentHookAbstract setExcludeClasses(final String... excludeClasses) { + if (includeClasses != null) + throw new IllegalStateException("Cannot exclude classes if include classes has been set"); + this.excludeClasses = excludeClasses; + return this; + } + + protected boolean filterBySchemaClass(final ODocument iDocument) { + if (includeClasses == null && excludeClasses == null) + return true; + + final OClass clazz = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (clazz == null) + return false; + + if (includeClasses != null) { + // FILTER BY CLASSES + for (String cls : includeClasses) + if (clazz.isSubClassOf(cls)) + return true; + return false; + } + + if (excludeClasses != null) { + // FILTER BY CLASSES + for (String cls : excludeClasses) + if (clazz.isSubClassOf(cls)) + return false; + } + + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHook.java b/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHook.java new file mode 100755 index 00000000000..b605f4170cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHook.java @@ -0,0 +1,157 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.hook; + +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Hook interface to catch all events regarding records. + * + * @author Luca Garulli + * @author Sergey Sitnikov – scoped hooks + * @see ORecordHookAbstract + */ +public interface ORecordHook { + enum DISTRIBUTED_EXECUTION_MODE { + TARGET_NODE, SOURCE_NODE, BOTH + } + + enum HOOK_POSITION { + FIRST, EARLY, REGULAR, LATE, LAST + } + + enum TYPE { + ANY, + + BEFORE_CREATE, BEFORE_READ, BEFORE_UPDATE, BEFORE_DELETE, AFTER_CREATE, AFTER_READ, AFTER_UPDATE, AFTER_DELETE, + + CREATE_FAILED, READ_FAILED, UPDATE_FAILED, DELETE_FAILED, CREATE_REPLICATED, READ_REPLICATED, UPDATE_REPLICATED, + + DELETE_REPLICATED, FINALIZE_UPDATE, FINALIZE_CREATION, FINALIZE_DELETION + } + + enum RESULT { + RECORD_NOT_CHANGED, RECORD_CHANGED, SKIP, SKIP_IO, RECORD_REPLACED + } + + /** + *

      Defines available scopes for scoped hooks. + * + *

      Basically, each scope defines some subset of {@link ORecordHook.TYPE}, this + * limits the set of events the hook interested in and lowers the number of useless hook invocations. + * + * @see Scoped#getScopes() + */ + enum SCOPE { + /** + * The create scope, includes: {@link ORecordHook.TYPE#BEFORE_CREATE}, {@link ORecordHook.TYPE#AFTER_CREATE}, + * {@link ORecordHook.TYPE#FINALIZE_CREATION}, {@link ORecordHook.TYPE#CREATE_REPLICATED} and + * {@link ORecordHook.TYPE#CREATE_FAILED}. + */ + CREATE, + + /** + * The read scope, includes: {@link ORecordHook.TYPE#BEFORE_READ}, {@link ORecordHook.TYPE#AFTER_READ}, + * {@link ORecordHook.TYPE#READ_REPLICATED} and {@link ORecordHook.TYPE#READ_FAILED}. + */ + READ, + + /** + * The update scope, includes: {@link ORecordHook.TYPE#BEFORE_UPDATE}, {@link ORecordHook.TYPE#AFTER_UPDATE}, + * {@link ORecordHook.TYPE#FINALIZE_UPDATE}, {@link ORecordHook.TYPE#UPDATE_REPLICATED} and + * {@link ORecordHook.TYPE#UPDATE_FAILED}. + */ + UPDATE, + + /** + * The delete scope, includes: {@link ORecordHook.TYPE#BEFORE_DELETE}, {@link ORecordHook.TYPE#AFTER_DELETE}, + * {@link ORecordHook.TYPE#DELETE_REPLICATED}, {@link ORecordHook.TYPE#FINALIZE_DELETION} and + * {@link ORecordHook.TYPE#DELETE_FAILED}. + */ + DELETE; + + /** + * Maps the {@link ORecordHook.TYPE} to {@link ORecordHook.SCOPE}. + * + * @param type the hook type to map. + * + * @return the mapped scope. + */ + public static SCOPE typeToScope(TYPE type) { + switch (type) { + case BEFORE_CREATE: + case AFTER_CREATE: + case CREATE_FAILED: + case CREATE_REPLICATED: + case FINALIZE_CREATION: + return SCOPE.CREATE; + + case BEFORE_READ: + case AFTER_READ: + case READ_REPLICATED: + case READ_FAILED: + return SCOPE.READ; + + case BEFORE_UPDATE: + case AFTER_UPDATE: + case UPDATE_FAILED: + case UPDATE_REPLICATED: + case FINALIZE_UPDATE: + return SCOPE.UPDATE; + + case BEFORE_DELETE: + case AFTER_DELETE: + case DELETE_FAILED: + case DELETE_REPLICATED: + case FINALIZE_DELETION: + return SCOPE.DELETE; + + default: + throw new IllegalStateException("Unexpected hook type."); + } + } + } + + void onUnregister(); + + RESULT onTrigger(TYPE iType, ORecord iRecord); + + DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode(); + + /** + * Defines the contract of scoped hooks, extends the {@link ORecordHook} with scopes support. + */ + interface Scoped extends ORecordHook { + /** + *

      Returns the array of scopes this hook interested in. By default, all available scopes are returned, implement/override + * this method to limit the scopes this hook may participate to lower the number of useless invocations of this hook. + * + *

      Limiting the hook to proper scopes may give huge performance boost, especially if the hook + * {@link #onTrigger(TYPE, ORecord)} dispatcher implementation is heavy. In extreme cases, you may override the {@link + * #onTrigger(TYPE, ORecord)} to act directly on event {@link ORecordHook.TYPE} and exit early, scopes are just a more handy + * alternative to this. + * + * @return the scopes of this hook. + * + * @see ORecordHook.SCOPE + */ + SCOPE[] getScopes(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHookAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHookAbstract.java new file mode 100755 index 00000000000..8076d59da6f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/hook/ORecordHookAbstract.java @@ -0,0 +1,258 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.hook; + +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Hook abstract class that calls separate methods for each hook defined. + * + * @author Luca Garulli + * @see ORecordHook + */ +public abstract class ORecordHookAbstract implements ORecordHook { + + /** + * Called on unregistration. + */ + public void onUnregister() { + } + + /** + * It's called just before to create the new iRecord. + * + * @param iRecord + * The iRecord to create + * @return True if the iRecord has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeCreate(final ORecord iRecord) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the iRecord is created. + * + * @param iRecord + * The iRecord just created + */ + public void onRecordAfterCreate(final ORecord iRecord) { + } + + public void onRecordCreateFailed(final ORecord iRecord) { + } + + public void onRecordCreateReplicated(final ORecord iRecord) { + } + + /** + * It's called just before to read the iRecord. + * + * @param iRecord + * The iRecord to read + * @return + */ + public RESULT onRecordBeforeRead(final ORecord iRecord) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the iRecord is read. + * + * @param iRecord + * The iRecord just read + */ + public void onRecordAfterRead(final ORecord iRecord) { + } + + public void onRecordReadFailed(final ORecord iRecord) { + } + + public void onRecordReadReplicated(final ORecord iRecord) { + } + + /** + * It's called just before to update the iRecord. + * + * @param iRecord + * The iRecord to update + * @return True if the iRecord has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeUpdate(final ORecord iRecord) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the iRecord is updated. + * + * @param iRecord + * The iRecord just updated + */ + public void onRecordAfterUpdate(final ORecord iRecord) { + } + + public void onRecordUpdateFailed(final ORecord iRecord) { + } + + public void onRecordUpdateReplicated(final ORecord iRecord) { + } + + /** + * It's called just before to delete the iRecord. + * + * @param iRecord + * The iRecord to delete + * @return True if the iRecord has been modified and a new marshalling is required, otherwise false + */ + public RESULT onRecordBeforeDelete(final ORecord iRecord) { + return RESULT.RECORD_NOT_CHANGED; + } + + /** + * It's called just after the iRecord is deleted. + * + * @param iRecord + * The iRecord just deleted + */ + public void onRecordAfterDelete(final ORecord iRecord) { + } + + public void onRecordDeleteFailed(final ORecord iRecord) { + } + + public void onRecordDeleteReplicated(final ORecord iRecord) { + } + + public RESULT onRecordBeforeReplicaAdd(final ORecord record) { + return RESULT.RECORD_NOT_CHANGED; + } + + public void onRecordAfterReplicaAdd(final ORecord record) { + } + + public void onRecordReplicaAddFailed(final ORecord record) { + } + + public RESULT onRecordBeforeReplicaUpdate(final ORecord record) { + return RESULT.RECORD_NOT_CHANGED; + } + + public void onRecordAfterReplicaUpdate(final ORecord record) { + } + + public void onRecordReplicaUpdateFailed(final ORecord record) { + } + + public RESULT onRecordBeforeReplicaDelete(final ORecord record) { + return RESULT.RECORD_NOT_CHANGED; + } + + public void onRecordAfterReplicaDelete(final ORecord record) { + } + + public void onRecordReplicaDeleteFailed(final ORecord record) { + } + + public void onRecordFinalizeUpdate(final ORecord record) { + } + + public void onRecordFinalizeCreation(final ORecord record) { + } + + public void onRecordFinalizeDeletion(final ORecord record) { + } + + public RESULT onTrigger(final TYPE iType, final ORecord record) { + switch (iType) { + case BEFORE_CREATE: + return onRecordBeforeCreate(record); + + case AFTER_CREATE: + onRecordAfterCreate(record); + break; + + case CREATE_FAILED: + onRecordCreateFailed(record); + break; + + case CREATE_REPLICATED: + onRecordCreateReplicated(record); + break; + + case BEFORE_READ: + return onRecordBeforeRead(record); + + case AFTER_READ: + onRecordAfterRead(record); + break; + + case READ_FAILED: + onRecordReadFailed(record); + break; + + case READ_REPLICATED: + onRecordReadReplicated(record); + break; + + case BEFORE_UPDATE: + return onRecordBeforeUpdate(record); + + case AFTER_UPDATE: + onRecordAfterUpdate(record); + break; + + case UPDATE_FAILED: + onRecordUpdateFailed(record); + break; + + case UPDATE_REPLICATED: + onRecordUpdateReplicated(record); + break; + + case BEFORE_DELETE: + return onRecordBeforeDelete(record); + + case AFTER_DELETE: + onRecordAfterDelete(record); + break; + + case DELETE_FAILED: + onRecordDeleteFailed(record); + break; + + case DELETE_REPLICATED: + onRecordDeleteReplicated(record); + break; + + case FINALIZE_CREATION: + onRecordFinalizeCreation(record); + break; + + case FINALIZE_UPDATE: + onRecordFinalizeUpdate(record); + break; + + case FINALIZE_DELETION: + onRecordFinalizeDeletion(record); + break; + + } + return RESULT.RECORD_NOT_CHANGED; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/id/OContextualRecordId.java b/core/src/main/java/com/orientechnologies/orient/core/id/OContextualRecordId.java new file mode 100644 index 00000000000..db1d25d1c01 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/id/OContextualRecordId.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2014 Orient Technologies. + * * + * * Licensed 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 com.orientechnologies.orient.core.id; + +import java.util.Map; + +public class OContextualRecordId extends ORecordId { + + protected Map context; + + public OContextualRecordId(final String iRecordId) { + super(iRecordId); + } + + public OContextualRecordId setContext(final Map context) { + this.context = context; + return this; + } + + public Map getContext() { + return context; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/id/OImmutableRecordId.java b/core/src/main/java/com/orientechnologies/orient/core/id/OImmutableRecordId.java new file mode 100755 index 00000000000..02dcecc8b13 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/id/OImmutableRecordId.java @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.id; + +import java.io.IOException; +import java.io.InputStream; + +import com.orientechnologies.orient.core.serialization.OMemoryStream; + +/** + * Immutable ORID implementation. To be really immutable fields must not be public anymore. TODO! + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OImmutableRecordId extends ORecordId { + private static final long serialVersionUID = 1L; + + public OImmutableRecordId(final int iClusterId, final long iClusterPosition) { + super(iClusterId, iClusterPosition); + } + + public OImmutableRecordId(final ORecordId iRID) { + super(iRID); + } + + @Override + public void copyFrom(final ORID iSource) { + throw new UnsupportedOperationException("copyFrom"); + } + + @Override + public ORecordId fromStream(byte[] iBuffer) { + throw new UnsupportedOperationException("fromStream"); + } + + @Override + public ORecordId fromStream(OMemoryStream iStream) { + throw new UnsupportedOperationException("fromStream"); + } + + @Override + public ORecordId fromStream(InputStream iStream) throws IOException { + throw new UnsupportedOperationException("fromStream"); + } + + @Override + public void fromString(String iRecordId) { + throw new UnsupportedOperationException("fromString"); + } + + @Override + public void reset() { + throw new UnsupportedOperationException("reset"); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/id/ORID.java b/core/src/main/java/com/orientechnologies/orient/core/id/ORID.java new file mode 100644 index 00000000000..dac03b024cf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/id/ORID.java @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.id; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * RecordID interface that represents a recordid in database. RecordID are made of 2 numbers: cluster id (cluster number) and + * cluster position (absolute position inside the cluster). Loading a record by its RecordID allows O(1) performance, no matter the + * database size. + * + * @author Luca Garulli + */ +public interface ORID extends OIdentifiable, OSerializableStream { + char PREFIX = '#'; + char SEPARATOR = ':'; + int CLUSTER_MAX = 32767; + int CLUSTER_ID_INVALID = -1; + long CLUSTER_POS_INVALID = -1; + + int getClusterId(); + + long getClusterPosition(); + + void reset(); + + boolean isPersistent(); + + boolean isValid(); + + boolean isNew(); + + boolean isTemporary(); + + ORID copy(); + + String next(); + + /** + * Deprecated since v2.2 + */ + @Deprecated + ORID nextRid(); + + int toStream(OutputStream iStream) throws IOException; + + StringBuilder toString(StringBuilder iBuffer); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/id/ORecordId.java b/core/src/main/java/com/orientechnologies/orient/core/id/ORecordId.java new file mode 100755 index 00000000000..2c5c39cb7f3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/id/ORecordId.java @@ -0,0 +1,357 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.id; + +import java.io.*; +import java.util.List; + +import com.orientechnologies.common.util.OPatternConst; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.OMemoryStream; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.storage.OStorage; + +public class ORecordId implements ORID { + public static final ORecordId EMPTY_RECORD_ID = new ORecordId(); + public static final byte[] EMPTY_RECORD_ID_STREAM = EMPTY_RECORD_ID.toStream(); + public static final int PERSISTENT_SIZE = OBinaryProtocol.SIZE_SHORT + OBinaryProtocol.SIZE_LONG; + private static final long serialVersionUID = 247070594054408657L; + // INT TO AVOID JVM PENALTY, BUT IT'S STORED AS SHORT + private int clusterId = CLUSTER_ID_INVALID; + private long clusterPosition = CLUSTER_POS_INVALID; + + public ORecordId() { + } + + public ORecordId(final int iClusterId, final long iPosition) { + clusterId = iClusterId; + checkClusterLimits(); + clusterPosition = iPosition; + } + + public ORecordId(final int iClusterIdId) { + clusterId = iClusterIdId; + checkClusterLimits(); + } + + public ORecordId(final String iRecordId) { + fromString(iRecordId); + } + + /** + * Copy constructor. + * + * @param parentRid Source object + */ + public ORecordId(final ORID parentRid) { + this.clusterId = parentRid.getClusterId(); + this.clusterPosition = parentRid.getClusterPosition(); + } + + public static String generateString(final int iClusterId, final long iPosition) { + final StringBuilder buffer = new StringBuilder(12); + buffer.append(PREFIX); + buffer.append(iClusterId); + buffer.append(SEPARATOR); + buffer.append(iPosition); + return buffer.toString(); + } + + public static boolean isValid(final long pos) { + return pos != CLUSTER_POS_INVALID; + } + + public static boolean isPersistent(final long pos) { + return pos > CLUSTER_POS_INVALID; + } + + public static boolean isNew(final long pos) { + return pos < 0; + } + + public static boolean isTemporary(final long clusterPosition) { + return clusterPosition < CLUSTER_POS_INVALID; + } + + public static boolean isA(final String iString) { + return OPatternConst.PATTERN_RID.matcher(iString).matches(); + } + + public void reset() { + clusterId = CLUSTER_ID_INVALID; + clusterPosition = CLUSTER_POS_INVALID; + } + + public boolean isValid() { + return getClusterPosition() != CLUSTER_POS_INVALID; + } + + public boolean isPersistent() { + return getClusterId() > -1 && getClusterPosition() > CLUSTER_POS_INVALID; + } + + public boolean isNew() { + return getClusterPosition() < 0; + } + + public boolean isTemporary() { + return getClusterId() != -1 && getClusterPosition() < CLUSTER_POS_INVALID; + } + + @Override + public String toString() { + return generateString(getClusterId(), getClusterPosition()); + } + + public StringBuilder toString(StringBuilder iBuffer) { + if (iBuffer == null) + iBuffer = new StringBuilder(); + + iBuffer.append(PREFIX); + iBuffer.append(clusterId); + iBuffer.append(SEPARATOR); + iBuffer.append(getClusterPosition()); + return iBuffer; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof OIdentifiable)) + return false; + final ORecordId other = (ORecordId) ((OIdentifiable) obj).getIdentity(); + + if (getClusterId() != other.getClusterId()) + return false; + if (getClusterPosition() != other.getClusterPosition()) + return false; + return true; + } + + @Override + public int hashCode() { + return 31 * getClusterId() + 103 * (int) getClusterPosition(); + } + + public int compareTo(final OIdentifiable iOther) { + if (iOther == this) + return 0; + + if (iOther == null) + return 1; + + final int otherClusterId = iOther.getIdentity().getClusterId(); + if (getClusterId() == otherClusterId) { + final long otherClusterPos = iOther.getIdentity().getClusterPosition(); + + return (getClusterPosition() < otherClusterPos) ? -1 : ((getClusterPosition() == otherClusterPos) ? 0 : 1); + } else if (getClusterId() > otherClusterId) + return 1; + + return -1; + } + + public int compare(final OIdentifiable iObj1, final OIdentifiable iObj2) { + if (iObj1 == iObj2) + return 0; + + if (iObj1 != null) + return iObj1.compareTo(iObj2); + + return -1; + } + + public ORecordId copy() { + return new ORecordId(getClusterId(), getClusterPosition()); + } + + public void toStream(final DataOutput out) throws IOException { + out.writeShort(getClusterId()); + out.writeLong(getClusterPosition()); + } + + public void fromStream(final DataInput in) throws IOException { + setClusterId(in.readShort()); + setClusterPosition(in.readLong()); + } + + public ORecordId fromStream(final InputStream iStream) throws IOException { + setClusterId(OBinaryProtocol.bytes2short(iStream)); + setClusterPosition(OBinaryProtocol.bytes2long(iStream)); + return this; + } + + public ORecordId fromStream(final OMemoryStream iStream) { + setClusterId(iStream.getAsShort()); + setClusterPosition(iStream.getAsLong()); + return this; + } + + public ORecordId fromStream(final byte[] iBuffer) { + if (iBuffer != null) { + setClusterId(OBinaryProtocol.bytes2short(iBuffer, 0)); + setClusterPosition(OBinaryProtocol.bytes2long(iBuffer, OBinaryProtocol.SIZE_SHORT)); + } + return this; + } + + public int toStream(final OutputStream iStream) throws IOException { + final int beginOffset = OBinaryProtocol.short2bytes((short) getClusterId(), iStream); + OBinaryProtocol.long2bytes(getClusterPosition(), iStream); + return beginOffset; + } + + public int toStream(final OMemoryStream iStream) throws IOException { + final int beginOffset = OBinaryProtocol.short2bytes((short) getClusterId(), iStream); + OBinaryProtocol.long2bytes(getClusterPosition(), iStream); + return beginOffset; + } + + public byte[] toStream() { + final byte[] buffer = new byte[OBinaryProtocol.SIZE_SHORT + OBinaryProtocol.SIZE_LONG]; + + OBinaryProtocol.short2bytes((short) getClusterId(), buffer, 0); + OBinaryProtocol.long2bytes(getClusterPosition(), buffer, OBinaryProtocol.SIZE_SHORT); + + return buffer; + } + + public int getClusterId() { + return clusterId; + } + + public long getClusterPosition() { + return clusterPosition; + } + + public void fromString(String iRecordId) { + if (iRecordId != null) + iRecordId = iRecordId.trim(); + + if (iRecordId == null || iRecordId.isEmpty()) { + setClusterId(CLUSTER_ID_INVALID); + setClusterPosition(CLUSTER_POS_INVALID); + return; + } + + if (!OStringSerializerHelper.contains(iRecordId, SEPARATOR)) + throw new IllegalArgumentException( + "Argument '" + iRecordId + "' is not a RecordId in form of string. Format must be: :"); + + final List parts = OStringSerializerHelper.split(iRecordId, SEPARATOR, PREFIX); + + if (parts.size() != 2) + throw new IllegalArgumentException("Argument received '" + iRecordId + + "' is not a RecordId in form of string. Format must be: #:. Example: #3:12"); + + setClusterId(Integer.parseInt(parts.get(0))); + checkClusterLimits(); + setClusterPosition(Long.parseLong(parts.get(1))); + } + + public void copyFrom(final ORID iSource) { + if (iSource == null) + throw new IllegalArgumentException("Source is null"); + + setClusterId(iSource.getClusterId()); + setClusterPosition(iSource.getClusterPosition()); + } + + @Override + public void lock(final boolean iExclusive) { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction() + .lockRecord(this, iExclusive ? OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK : OStorage.LOCKING_STRATEGY.SHARED_LOCK); + } + + @Override + public boolean isLocked() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().isLockedRecord(this); + } + + @Override + public OStorage.LOCKING_STRATEGY lockingStrategy() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().lockingStrategy(this); + } + + @Override + public void unlock() { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().unlockRecord(this); + } + + public String next() { + return generateString(getClusterId(), getClusterPosition() + 1); + } + + @Override + public ORID nextRid() { + return new ORecordId(getClusterId(), getClusterPosition() + 1); + } + + public ORID getIdentity() { + return this; + } + + @SuppressWarnings("unchecked") + public T getRecord() { + if (!isValid()) + return null; + + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (db == null) + throw new ODatabaseException( + "No database found in current thread local space. If you manually control databases over threads assure to set the current database before to use it by calling: ODatabaseRecordThreadLocal.INSTANCE.set(db);"); + + return (T) db.load(this); + } + + private void checkClusterLimits() { + if (getClusterId() < -2) + throw new ODatabaseException("RecordId cannot support negative cluster id. Found: " + getClusterId()); + + if (getClusterId() > CLUSTER_MAX) + throw new ODatabaseException("RecordId cannot support cluster id major than 32767. Found: " + getClusterId()); + } + + private void checkClusterLimits(int clusterId) { + if (clusterId < -2) + throw new ODatabaseException("RecordId cannot support negative cluster id. Found: " + getClusterId()); + + if (clusterId > CLUSTER_MAX) + throw new ODatabaseException("RecordId cannot support cluster id major than 32767. Found: " + getClusterId()); + } + + public void setClusterId(int clusterId) { + checkClusterLimits(clusterId); + + this.clusterId = clusterId; + } + + public void setClusterPosition(long clusterPosition) { + this.clusterPosition = clusterPosition; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/ClassIndexManagerRemote.java b/core/src/main/java/com/orientechnologies/orient/core/index/ClassIndexManagerRemote.java new file mode 100644 index 00000000000..22f5460bc02 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/ClassIndexManagerRemote.java @@ -0,0 +1,36 @@ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Created by tglman on 29/04/16. + */ +public class ClassIndexManagerRemote extends OClassIndexManager { + + public ClassIndexManagerRemote(ODatabaseDocument database) { + super(database); + } + + @Override + public RESULT onTrigger(TYPE iType, ORecord iRecord) { + if (database.getTransaction().isActive()) + return super.onTrigger(iType, iRecord); + else + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + protected void putInIndex(OIndex index, Object key, OIdentifiable value) { + assert index instanceof OIndexTxAware; + ((OIndexTxAware) index).putOnlyClientTrack(key, value); + } + + @Override + protected void removeFromIndex(OIndex index, Object key, OIdentifiable value) { + assert index instanceof OIndexTxAware; + ((OIndexTxAware) index).removeOnlyClientTrack(key, value); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinition.java new file mode 100755 index 00000000000..413d306bf77 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinition.java @@ -0,0 +1,98 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass; + +/** + * Abstract index definiton implementation. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public abstract class OAbstractIndexDefinition extends ODocumentWrapperNoClass implements OIndexDefinition { + protected OCollate collate = new ODefaultCollate(); + private boolean nullValuesIgnored = true; + + protected OAbstractIndexDefinition() { + super(new ODocument().setTrackingChanges(false)); + } + + public OCollate getCollate() { + return collate; + } + + public void setCollate(final OCollate collate) { + if (collate == null) + throw new IllegalArgumentException("COLLATE cannot be null"); + this.collate = collate; + } + + public void setCollate(String iCollate) { + if (iCollate == null) + iCollate = ODefaultCollate.NAME; + + setCollate(OSQLEngine.getCollate(iCollate)); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OAbstractIndexDefinition that = (OAbstractIndexDefinition) o; + + if (!collate.equals(that.collate)) + return false; + + if (nullValuesIgnored != that.nullValuesIgnored) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = collate.hashCode(); + result = 31 * result + (nullValuesIgnored ? 1 : 0); + return result; + } + + @Override + public boolean isNullValuesIgnored() { + return nullValuesIgnored; + } + + @Override + public void setNullValuesIgnored(boolean value) { + nullValuesIgnored = value; + } + + protected void serializeToStream() { + } + + protected void serializeFromStream() { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinitionMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinitionMultiValue.java new file mode 100755 index 00000000000..040a0c26d09 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OAbstractIndexDefinitionMultiValue.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import java.util.Map; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Base class for all multivalue index definitions that contains base functionality which can be reused by concrete implementations. + */ +public abstract class OAbstractIndexDefinitionMultiValue extends OPropertyIndexDefinition implements OIndexDefinitionMultiValue { + protected OAbstractIndexDefinitionMultiValue() { + } + + protected OAbstractIndexDefinitionMultiValue(final String iClassName, final String iField, final OType iType) { + super(iClassName, iField, iType); + } + + protected void processAdd(final Object value, final Map keysToAdd, final Map keysToRemove) { + if (value == null) + return; + + final Integer removeCount = keysToRemove.get(value); + if (removeCount != null) { + int newRemoveCount = removeCount - 1; + if (newRemoveCount > 0) + keysToRemove.put(value, newRemoveCount); + else + keysToRemove.remove(value); + } else { + final Integer addCount = keysToAdd.get(value); + if (addCount != null) + keysToAdd.put(value, addCount + 1); + else + keysToAdd.put(value, 1); + } + } + + protected void processRemoval(final Object value, final Map keysToAdd, final Map keysToRemove) { + if (value == null) + return; + + final Integer addCount = keysToAdd.get(value); + if (addCount != null) { + int newAddCount = addCount - 1; + if (newAddCount > 0) + keysToAdd.put(value, newAddCount); + else + keysToAdd.remove(value); + } else { + final Integer removeCount = keysToRemove.get(value); + if (removeCount != null) + keysToRemove.put(value, removeCount + 1); + else + keysToRemove.put(value, 1); + } + } + + @Override + public boolean isAutomatic() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysGreaterKey.java b/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysGreaterKey.java new file mode 100755 index 00000000000..f050f6d3c4e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysGreaterKey.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Key that is used in {@link com.orientechnologies.orient.core.index.mvrbtree.OMVRBTree} for partial composite key search. + * It always greater than any passed in key. + * + * @author Andrey Lomakin + * @since 20.03.12 + */ +@SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") +public final class OAlwaysGreaterKey implements Comparable>{ + public int compareTo(Comparable o) { + return 1; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysLessKey.java b/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysLessKey.java new file mode 100755 index 00000000000..1e583a74fcf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OAlwaysLessKey.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Key that is used in {@link com.orientechnologies.orient.core.index.mvrbtree.OMVRBTree} for partial composite key search. + * It always lesser than any passed in key. + * + * @author Andrey Lomakin + * @since 20.03.12 + */ +@SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") +public final class OAlwaysLessKey implements Comparable> { + public int compareTo(Comparable o) { + return -1; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OClassIndexManager.java b/core/src/main/java/com/orientechnologies/orient/core/index/OClassIndexManager.java new file mode 100755 index 00000000000..38e937b2e8c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OClassIndexManager.java @@ -0,0 +1,669 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.OHookReplacedRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.exception.OFastConcurrentModificationException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; + +import java.util.*; +import java.util.concurrent.locks.Lock; + +import static com.orientechnologies.orient.core.hook.ORecordHook.TYPE.BEFORE_CREATE; +import static com.orientechnologies.orient.core.hook.ORecordHook.TYPE.BEFORE_UPDATE; + +/** + * Handles indexing when records change. + * + * @author Andrey Lomakin, Artem Orobets + */ +public class OClassIndexManager extends ODocumentHookAbstract + implements ORecordHook.Scoped, OOrientStartupListener, OOrientShutdownListener { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + private Deque> lockedKeys = new ArrayDeque>(); + + public OClassIndexManager(ODatabaseDocument database) { + super(database); + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + @Override + public void onShutdown() { + lockedKeys = null; + } + + @Override + public void onStartup() { + if (lockedKeys == null) + lockedKeys = new ArrayDeque>(); + } + + private void processCompositeIndexUpdate(final OIndex index, final Set dirtyFields, final ODocument iRecord) { + final OCompositeIndexDefinition indexDefinition = (OCompositeIndexDefinition) index.getDefinition(); + + final List indexFields = indexDefinition.getFields(); + final String multiValueField = indexDefinition.getMultiValueField(); + + for (final String indexField : indexFields) { + if (dirtyFields.contains(indexField)) { + final List origValues = new ArrayList(indexFields.size()); + + for (final String field : indexFields) { + if (!field.equals(multiValueField)) + if (dirtyFields.contains(field)) { + origValues.add(iRecord.getOriginalValue(field)); + } else { + origValues.add(iRecord.field(field)); + } + } + + if (multiValueField == null) { + final Object origValue = indexDefinition.createValue(origValues); + final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord); + + if (!indexDefinition.isNullValuesIgnored() || origValue != null) + removeFromIndex(index, origValue, iRecord); + + if (!indexDefinition.isNullValuesIgnored() || newValue != null) + putInIndex(index, newValue, iRecord.getIdentity()); + } else { + final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(multiValueField); + if (multiValueChangeTimeLine == null) { + if (dirtyFields.contains(multiValueField)) + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.getOriginalValue(multiValueField)); + else + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.field(multiValueField)); + + final Object origValue = indexDefinition.createValue(origValues); + final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord); + + processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue); + } else { + //in case of null values support and empty collection field we put null placeholder in + //place where collection item should be located so we can not use "fast path" to + //update index values + if (dirtyFields.size() == 1 && indexDefinition.isNullValuesIgnored()) { + final Map keysToAdd = new HashMap(); + final Map keysToRemove = new HashMap(); + + for (OMultiValueChangeEvent changeEvent : multiValueChangeTimeLine.getMultiValueChangeEvents()) { + indexDefinition.processChangeEvent(changeEvent, keysToAdd, keysToRemove, origValues.toArray()); + } + + for (final Object keyToRemove : keysToRemove.keySet()) + removeFromIndex(index, keyToRemove, iRecord); + + for (final Object keyToAdd : keysToAdd.keySet()) + putInIndex(index, keyToAdd, iRecord.getIdentity()); + } else { + final OTrackedMultiValue fieldValue = iRecord.field(multiValueField); + final Object restoredMultiValue = fieldValue + .returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents()); + + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), restoredMultiValue); + + final Object origValue = indexDefinition.createValue(origValues); + final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord); + + processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue); + } + } + } + return; + } + } + } + + private void processSingleIndexUpdate(final OIndex index, final Set dirtyFields, final ODocument iRecord) { + final OIndexDefinition indexDefinition = index.getDefinition(); + final List indexFields = indexDefinition.getFields(); + + if (indexFields.isEmpty()) + return; + + final String indexField = indexFields.get(0); + if (!dirtyFields.contains(indexField)) + return; + + final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(indexField); + if (multiValueChangeTimeLine != null) { + final OIndexDefinitionMultiValue indexDefinitionMultiValue = (OIndexDefinitionMultiValue) indexDefinition; + final Map keysToAdd = new HashMap(); + final Map keysToRemove = new HashMap(); + + for (OMultiValueChangeEvent changeEvent : multiValueChangeTimeLine.getMultiValueChangeEvents()) { + indexDefinitionMultiValue.processChangeEvent(changeEvent, keysToAdd, keysToRemove); + } + + for (final Object keyToRemove : keysToRemove.keySet()) + removeFromIndex(index, keyToRemove, iRecord); + + for (final Object keyToAdd : keysToAdd.keySet()) + putInIndex(index, keyToAdd, iRecord.getIdentity()); + + } else { + final Object origValue = indexDefinition.createValue(iRecord.getOriginalValue(indexField)); + final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord); + + processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue); + } + } + + private void processIndexUpdateFieldAssignment(OIndex index, ODocument iRecord, final Object origValue, + final Object newValue) { + + final OIndexDefinition indexDefinition = index.getDefinition(); + if ((origValue instanceof Collection) && (newValue instanceof Collection)) { + final Set valuesToRemove = new HashSet((Collection) origValue); + final Set valuesToAdd = new HashSet((Collection) newValue); + + valuesToRemove.removeAll((Collection) newValue); + valuesToAdd.removeAll((Collection) origValue); + + for (final Object valueToRemove : valuesToRemove) { + if (!indexDefinition.isNullValuesIgnored() || valueToRemove != null) { + removeFromIndex(index, valueToRemove, iRecord); + } + } + + for (final Object valueToAdd : valuesToAdd) { + if (!indexDefinition.isNullValuesIgnored() || valueToAdd != null) { + putInIndex(index, valueToAdd, iRecord); + } + } + } else { + deleteIndexKey(index, iRecord, origValue); + + if (newValue instanceof Collection) { + for (final Object newValueItem : (Collection) newValue) { + putInIndex(index, newValueItem, iRecord.getIdentity()); + } + } else if (!indexDefinition.isNullValuesIgnored() || newValue != null) { + putInIndex(index, newValue, iRecord.getIdentity()); + } + } + } + + private boolean processCompositeIndexDelete(final OIndex index, final Set dirtyFields, final ODocument iRecord) { + final OCompositeIndexDefinition indexDefinition = (OCompositeIndexDefinition) index.getDefinition(); + + final String multiValueField = indexDefinition.getMultiValueField(); + + final List indexFields = indexDefinition.getFields(); + for (final String indexField : indexFields) { + // REMOVE IT + if (dirtyFields.contains(indexField)) { + final List origValues = new ArrayList(indexFields.size()); + + for (final String field : indexFields) { + if (!field.equals(multiValueField)) + if (dirtyFields.contains(field)) + origValues.add(iRecord.getOriginalValue(field)); + else + origValues.add(iRecord.field(field)); + } + + if (multiValueField != null) { + final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(multiValueField); + if (multiValueChangeTimeLine != null) { + final OTrackedMultiValue fieldValue = iRecord.field(multiValueField); + final Object restoredMultiValue = fieldValue.returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents()); + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), restoredMultiValue); + } else if (dirtyFields.contains(multiValueField)) + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.getOriginalValue(multiValueField)); + else + origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.field(multiValueField)); + } + + final Object origValue = indexDefinition.createValue(origValues); + deleteIndexKey(index, iRecord, origValue); + + return true; + } + } + return false; + } + + private void deleteIndexKey(final OIndex index, final ODocument iRecord, final Object origValue) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + if (origValue instanceof Collection) { + for (final Object valueItem : (Collection) origValue) { + if (!indexDefinition.isNullValuesIgnored() || valueItem != null) + removeFromIndex(index, valueItem, iRecord); + } + } else if (!indexDefinition.isNullValuesIgnored() || origValue != null) { + removeFromIndex(index, origValue, iRecord); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean processSingleIndexDelete(final OIndex index, final Set dirtyFields, final ODocument iRecord) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final List indexFields = indexDefinition.getFields(); + if (indexFields.isEmpty()) { + return false; + } + + final String indexField = indexFields.iterator().next(); + if (dirtyFields.contains(indexField)) { + final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(indexField); + + final Object origValue; + if (multiValueChangeTimeLine != null) { + final OTrackedMultiValue fieldValue = iRecord.field(indexField); + final Object restoredMultiValue = fieldValue.returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents()); + origValue = indexDefinition.createValue(restoredMultiValue); + } else + origValue = indexDefinition.createValue(iRecord.getOriginalValue(indexField)); + + deleteIndexKey(index, iRecord, origValue); + return true; + } + return false; + } + + private ODocument checkIndexedPropertiesOnCreation(final ODocument record, final Collection> indexes) { + ODocument replaced = null; + + final TreeMap, List> indexKeysMap = new TreeMap, List>(); + + for (final OIndex index : indexes) { + if (index.getInternal() instanceof OIndexUnique) { + OIndexRecorder indexRecorder = new OIndexRecorder((OIndexUnique) index.getInternal()); + + addIndexEntry(record, record.getIdentity(), indexRecorder); + indexKeysMap.put(index, indexRecorder.getAffectedKeys()); + } + } + + if (noTx(record)) { + final List locks = new ArrayList(indexKeysMap.size()); + + for (Map.Entry, List> entry : indexKeysMap.entrySet()) { + final OIndexInternal index = entry.getKey().getInternal(); + locks.add(index.lockKeysForUpdate(entry.getValue())); + } + + lockedKeys.push(locks); + } + + for (Map.Entry, List> entry : indexKeysMap.entrySet()) { + final OIndex index = entry.getKey(); + + for (Object keyItem : entry.getValue()) { + final ODocument r = index.checkEntry(record, keyItem); + if (r != null) + if (replaced == null) + replaced = r; + else { + throw new OIndexException("Cannot merge record from multiple indexes. Use this strategy when you have only one index"); + } + } + } + + return replaced; + } + + private void checkIndexedPropertiesOnUpdate(final ODocument record, final Collection> indexes) { + final TreeMap, List> indexKeysMap = new TreeMap, List>(); + + final Set dirtyFields = new HashSet(Arrays.asList(record.getDirtyFields())); + if (dirtyFields.isEmpty()) + return; + + for (final OIndex index : indexes) { + + if (index.getInternal() instanceof OIndexUnique) { + final OIndexRecorder indexRecorder = new OIndexRecorder((OIndexInternal) index.getInternal()); + processIndexUpdate(record, dirtyFields, indexRecorder); + + indexKeysMap.put(index, indexRecorder.getAffectedKeys()); + } + } + + if (noTx(record)) { + final List locks = new ArrayList(indexKeysMap.size()); + + for (Map.Entry, List> entry : indexKeysMap.entrySet()) { + final OIndexInternal index = entry.getKey().getInternal(); + locks.add(index.lockKeysForUpdate(entry.getValue())); + } + + lockedKeys.push(locks); + } + + for (Map.Entry, List> entry : indexKeysMap.entrySet()) { + final OIndex index = entry.getKey(); + + for (Object keyItem : entry.getValue()) { + index.checkEntry(record, keyItem); + } + } + } + + private static ODocument checkForLoading(final ODocument iRecord) { + if (iRecord.getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) { + try { + return (ODocument) iRecord.load(); + } catch (final ORecordNotFoundException e) { + throw OException.wrapException(new OIndexException("Error during loading of record with id " + iRecord.getIdentity()), e); + } + } + return iRecord; + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } + + @Override + public RESULT onRecordBeforeCreate(final ODocument document) { + final ODocument replaced = checkIndexes(document, BEFORE_CREATE); + if (replaced != null) { + OHookReplacedRecordThreadLocal.INSTANCE.set(replaced); + return RESULT.RECORD_REPLACED; + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterCreate(ODocument document) { + document = checkForLoading(document); + + final OClass cls = ODocumentInternal.getImmutableSchemaClass(document); + if (cls != null) { + final Collection> indexes = cls.getIndexes(); + addIndexesEntries(document, indexes); + } + } + + @Override + public void onRecordCreateFailed(final ODocument iDocument) { + } + + @Override + public void onRecordCreateReplicated(final ODocument iDocument) { + } + + @Override + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + checkIndexes(iDocument, BEFORE_UPDATE); + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterUpdate(ODocument iDocument) { + iDocument = checkForLoading(iDocument); + + final OClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (cls == null) + return; + + final Collection> indexes = cls.getIndexes(); + + if (!indexes.isEmpty()) { + final Set dirtyFields = new HashSet(Arrays.asList(iDocument.getDirtyFields())); + + if (!dirtyFields.isEmpty()) { + for (final OIndex index : indexes) { + try { + processIndexUpdate(iDocument, dirtyFields, index); + } catch (ORecordDuplicatedException ex) { + iDocument.undo(); + iDocument.setDirty(); + database.save(iDocument); + throw ex; + } + } + } + } + } + + private void processIndexUpdate(ODocument iDocument, Set dirtyFields, OIndex index) { + if (index.getDefinition() instanceof OCompositeIndexDefinition) + processCompositeIndexUpdate(index, dirtyFields, iDocument); + else + processSingleIndexUpdate(index, dirtyFields, iDocument); + } + + @Override + public void onRecordUpdateFailed(final ODocument iDocument) { + } + + @Override + public void onRecordUpdateReplicated(final ODocument iDocument) { + } + + @Override + public RESULT onRecordBeforeDelete(final ODocument iDocument) { + final int version = iDocument.getVersion(); // Cache the transaction-provided value + if (iDocument.fields() == 0 && iDocument.getIdentity().isPersistent()) { + // FORCE LOADING OF CLASS+FIELDS TO USE IT AFTER ON onRecordAfterDelete METHOD + iDocument.reload(); + if (version > -1 && iDocument.getVersion() != version) // check for record version errors + if (OFastConcurrentModificationException.enabled()) + throw OFastConcurrentModificationException.instance(); + else + throw new OConcurrentModificationException(iDocument.getIdentity(), iDocument.getVersion(), version, + ORecordOperation.DELETED); + } + + final OClass class_ = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (class_ != null) { + final Collection> indexes = class_.getIndexes(); + + final TreeMap, List> indexKeysMap = new TreeMap, List>(); + for (final OIndex index : indexes) { + if (index.getInternal() instanceof OIndexUnique) { + OIndexRecorder indexRecorder = new OIndexRecorder((OIndexUnique) index.getInternal()); + + addIndexEntry(iDocument, iDocument.getIdentity(), indexRecorder); + indexKeysMap.put(index, indexRecorder.getAffectedKeys()); + } + } + + if (noTx(iDocument)) { + final List locks = new ArrayList(indexKeysMap.size()); + + for (Map.Entry, List> entry : indexKeysMap.entrySet()) { + final OIndexInternal index = entry.getKey().getInternal(); + locks.add(index.lockKeysForUpdate(entry.getValue())); + } + + lockedKeys.push(locks); + } + } + + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterDelete(final ODocument iDocument) { + deleteIndexEntries(iDocument); + } + + @Override + public void onRecordDeleteFailed(final ODocument iDocument) { + } + + @Override + public void onRecordDeleteReplicated(final ODocument iDocument) { + } + + private void addIndexesEntries(ODocument document, final Collection> indexes) { + // STORE THE RECORD IF NEW, OTHERWISE ITS RID + final OIdentifiable rid = document.getIdentity(); + + for (final OIndex index : indexes) { + addIndexEntry(document, rid, index); + } + } + + private void addIndexEntry(ODocument document, OIdentifiable rid, OIndex index) { + final OIndexDefinition indexDefinition = index.getDefinition(); + final Object key = indexDefinition.getDocumentValueToIndex(document); + if (key instanceof Collection) { + for (final Object keyItem : (Collection) key) + if (!indexDefinition.isNullValuesIgnored() || keyItem != null) + putInIndex(index, keyItem, rid); + } else if (!indexDefinition.isNullValuesIgnored() || key != null) + try { + putInIndex(index, key, rid); + } catch (ORecordDuplicatedException e) { + if (!database.getTransaction().isActive()) { + database.delete(document); + } + throw e; + } + } + + private void deleteIndexEntries(ODocument iDocument) { + final OClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (cls == null) + return; + + final Collection> indexes = new ArrayList>(cls.getIndexes()); + + if (!indexes.isEmpty()) { + final Set dirtyFields = new HashSet(Arrays.asList(iDocument.getDirtyFields())); + + if (!dirtyFields.isEmpty()) { + // REMOVE INDEX OF ENTRIES FOR THE OLD VALUES + final Iterator> indexIterator = indexes.iterator(); + + while (indexIterator.hasNext()) { + final OIndex index = indexIterator.next(); + + final boolean result; + if (index.getDefinition() instanceof OCompositeIndexDefinition) + result = processCompositeIndexDelete(index, dirtyFields, iDocument); + else + result = processSingleIndexDelete(index, dirtyFields, iDocument); + + if (result) + indexIterator.remove(); + } + } + + // REMOVE INDEX OF ENTRIES FOR THE NON CHANGED ONLY VALUES + for (final OIndex index : indexes) { + final Object key = index.getDefinition().getDocumentValueToIndex(iDocument); + deleteIndexKey(index, iDocument, key); + } + } + } + + private ODocument checkIndexes(ODocument document, TYPE hookType) { + document = checkForLoading(document); + + ODocument replaced = null; + + final OClass cls = ODocumentInternal.getImmutableSchemaClass(document); + if (cls != null) { + final Collection> indexes = cls.getIndexes(); + switch (hookType) { + case BEFORE_CREATE: + replaced = checkIndexedPropertiesOnCreation(document, indexes); + break; + case BEFORE_UPDATE: + checkIndexedPropertiesOnUpdate(document, indexes); + break; + default: + throw new IllegalArgumentException("Invalid hook type: " + hookType); + } + } + + return replaced; + } + + @Override + public void onRecordFinalizeUpdate(ODocument document) { + if (noTx(document)) + unlockKeys(); + } + + @Override + public void onRecordFinalizeCreation(ODocument document) { + if (noTx(document)) + unlockKeys(); + } + + @Override + public void onRecordFinalizeDeletion(ODocument document) { + if (noTx(document)) + unlockKeys(); + } + + private void unlockKeys() { + if (lockedKeys == null) + return; + + final List lockList = lockedKeys.poll(); + if (lockList == null) + return; + + for (Lock[] locks : lockList) { + for (Lock lock : locks) + try { + lock.unlock(); + } catch (RuntimeException e) { + OLogManager.instance().error(this, "Error during unlock of index key", e); + } + } + } + + protected void putInIndex(OIndex index, Object key, OIdentifiable value) { + index.put(key, value); + } + + protected void removeFromIndex(OIndex index, Object key, OIdentifiable value) { + index.remove(key, value); + } + + private static boolean noTx(ODocument document) { + return !document.getDatabase().getTransaction().isActive(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeCollate.java b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeCollate.java new file mode 100755 index 00000000000..a9acac357b3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeCollate.java @@ -0,0 +1,110 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.OCollate; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collate implementation used on composite indexes. + */ +public class OCompositeCollate implements OCollate { + private static final long serialVersionUID = 8683726773893639905L; + private final OAbstractIndexDefinition oCompositeIndexDefinition; + + /** + * @param oCompositeIndexDefinition + */ + public OCompositeCollate(final OAbstractIndexDefinition oCompositeIndexDefinition) { + this.oCompositeIndexDefinition = oCompositeIndexDefinition; + } + + private final List collates = new ArrayList(); + + public void addCollate(OCollate collate) { + collates.add(collate); + } + + @Override + public String getName() { + throw new UnsupportedOperationException("getName"); + } + + @SuppressWarnings("unchecked") + @Override + public Object transform(final Object obj) { + final List keys; + if (obj instanceof OCompositeKey) { + final OCompositeKey compositeKey = (OCompositeKey) obj; + keys = compositeKey.getKeys(); + } else if (obj instanceof List) { + keys = (List) obj; + } else { + throw new OIndexException("Impossible add as key of a CompositeIndex a value of type " + obj.getClass()); + } + + final OCompositeKey transformedKey = new OCompositeKey(); + + final int size = Math.min(keys.size(), collates.size()); + for (int i = 0; i < size; i++) { + final Object key = keys.get(i); + + final OCollate collate = collates.get(i); + transformedKey.addKey(collate.transform(key)); + } + + for (int i = size; i < keys.size(); i++) + transformedKey.addKey(keys.get(i)); + + return transformedKey; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OCompositeCollate that = (OCompositeCollate) o; + + if (!collates.equals(that.collates)) + return false; + + return true; + } + + @Override + public int hashCode() { + return collates.hashCode(); + } + + public List getCollates() { + return collates; + } + + @Override + public String toString() { + return "OCompositeCollate{" + "collates=" + collates + ", null values ignored = " + + this.oCompositeIndexDefinition.isNullValuesIgnored() + '}'; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexCursor.java b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexCursor.java new file mode 100755 index 00000000000..5d36dac025a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexCursor.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.*; + +/** + * @author Andrey Lomakin + */ +public class OCompositeIndexCursor extends OIndexAbstractCursor { + private Collection cursors; + private Iterator cursorIterator; + private OIndexCursor cursor; + + public OCompositeIndexCursor(Collection cursors) { + this.cursors = cursors; + + cursorIterator = this.cursors.iterator(); + if (cursorIterator.hasNext()) + cursor = cursorIterator.next(); + } + + @Override + public Map.Entry nextEntry() { + Map.Entry entry = null; + + while (entry == null && cursor != null) { + entry = cursor.nextEntry(); + + if (entry == null) { + if (cursorIterator.hasNext()) { + cursor = cursorIterator.next(); + } else { + cursor = null; + } + } + } + + return entry; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexDefinition.java new file mode 100755 index 00000000000..85d8000efcf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeIndexDefinition.java @@ -0,0 +1,590 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLCreateIndex; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +/** + * Index that consist of several indexDefinitions like {@link OPropertyIndexDefinition}. + */ + +public class OCompositeIndexDefinition extends OAbstractIndexDefinition { + private static final long serialVersionUID = -885861736290603016L; + private final List indexDefinitions; + private String className; + private int multiValueDefinitionIndex = -1; + private OCompositeCollate collate = new OCompositeCollate(this); + + public OCompositeIndexDefinition() { + indexDefinitions = new ArrayList(5); + } + + /** + * Constructor for new index creation. + * + * @param iClassName - name of class which is owner of this index + */ + public OCompositeIndexDefinition(final String iClassName) { + super(); + + indexDefinitions = new ArrayList(5); + className = iClassName; + } + + /** + * Constructor for new index creation. + * + * @param iClassName - name of class which is owner of this index + * @param iIndexes List of indexDefinitions to add in given index. + */ + public OCompositeIndexDefinition(final String iClassName, final List iIndexes, int version) { + super(); + + indexDefinitions = new ArrayList(5); + for (OIndexDefinition indexDefinition : iIndexes) { + indexDefinitions.add(indexDefinition); + collate.addCollate(indexDefinition.getCollate()); + + if (indexDefinition instanceof OIndexDefinitionMultiValue) + if (multiValueDefinitionIndex == -1) + multiValueDefinitionIndex = indexDefinitions.size() - 1; + else + throw new OIndexException("Composite key cannot contain more than one collection item"); + } + + className = iClassName; + } + + /** + * {@inheritDoc} + */ + public String getClassName() { + return className; + } + + /** + * Add new indexDefinition in current composite. + * + * @param indexDefinition Index to add. + */ + public void addIndex(final OIndexDefinition indexDefinition) { + indexDefinitions.add(indexDefinition); + if (indexDefinition instanceof OIndexDefinitionMultiValue) { + if (multiValueDefinitionIndex == -1) + multiValueDefinitionIndex = indexDefinitions.size() - 1; + else + throw new OIndexException("Composite key cannot contain more than one collection item"); + } + + collate.addCollate(indexDefinition.getCollate()); + } + + /** + * {@inheritDoc} + */ + public List getFields() { + final List fields = new LinkedList(); + for (final OIndexDefinition indexDefinition : indexDefinitions) { + fields.addAll(indexDefinition.getFields()); + } + return Collections.unmodifiableList(fields); + } + + /** + * {@inheritDoc} + */ + public List getFieldsToIndex() { + final List fields = new LinkedList(); + for (final OIndexDefinition indexDefinition : indexDefinitions) { + fields.addAll(indexDefinition.getFieldsToIndex()); + } + return Collections.unmodifiableList(fields); + } + + /** + * {@inheritDoc} + */ + public Object getDocumentValueToIndex(final ODocument iDocument) { + final List compositeKeys = new ArrayList(10); + final OCompositeKey firstKey = new OCompositeKey(); + boolean containsCollection = false; + + compositeKeys.add(firstKey); + + for (final OIndexDefinition indexDefinition : indexDefinitions) { + final Object result = indexDefinition.getDocumentValueToIndex(iDocument); + + if (result == null && isNullValuesIgnored()) + return null; + + //for empty collections we add null key in index + if (result instanceof Collection && ((Collection) result).isEmpty() && isNullValuesIgnored()) + return null; + + containsCollection = addKey(firstKey, compositeKeys, containsCollection, result); + } + + if (!containsCollection) + return firstKey; + + return compositeKeys; + } + + public int getMultiValueDefinitionIndex() { + return multiValueDefinitionIndex; + } + + public String getMultiValueField() { + if (multiValueDefinitionIndex >= 0) + return indexDefinitions.get(multiValueDefinitionIndex).getFields().get(0); + + return null; + } + + /** + * {@inheritDoc} + */ + public Object createValue(final List params) { + int currentParamIndex = 0; + final OCompositeKey firstKey = new OCompositeKey(); + + final List compositeKeys = new ArrayList(10); + compositeKeys.add(firstKey); + + boolean containsCollection = false; + + for (final OIndexDefinition indexDefinition : indexDefinitions) { + if (currentParamIndex + 1 > params.size()) + break; + + final int endIndex; + if (currentParamIndex + indexDefinition.getParamCount() > params.size()) + endIndex = params.size(); + else + endIndex = currentParamIndex + indexDefinition.getParamCount(); + + final List indexParams = params.subList(currentParamIndex, endIndex); + currentParamIndex += indexDefinition.getParamCount(); + + final Object keyValue = indexDefinition.createValue(indexParams); + + if (keyValue == null && isNullValuesIgnored()) + return null; + + //for empty collections we add null key in index + if (keyValue instanceof Collection && ((Collection) keyValue).isEmpty() && isNullValuesIgnored()) + return null; + + containsCollection = addKey(firstKey, compositeKeys, containsCollection, keyValue); + } + + if (!containsCollection) + return firstKey; + + return compositeKeys; + } + + public OIndexDefinitionMultiValue getMultiValueDefinition() { + if (multiValueDefinitionIndex > -1) + return (OIndexDefinitionMultiValue) indexDefinitions.get(multiValueDefinitionIndex); + + return null; + } + + public OCompositeKey createSingleValue(final List params) { + final OCompositeKey compositeKey = new OCompositeKey(); + int currentParamIndex = 0; + + for (final OIndexDefinition indexDefinition : indexDefinitions) { + if (currentParamIndex + 1 > params.size()) + break; + + final int endIndex; + if (currentParamIndex + indexDefinition.getParamCount() > params.size()) + endIndex = params.size(); + else + endIndex = currentParamIndex + indexDefinition.getParamCount(); + + final List indexParams = params.subList(currentParamIndex, endIndex); + currentParamIndex += indexDefinition.getParamCount(); + + final Object keyValue; + + if (indexDefinition instanceof OIndexDefinitionMultiValue) + keyValue = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(indexParams.toArray()); + else + keyValue = indexDefinition.createValue(indexParams); + + if (keyValue == null && isNullValuesIgnored()) + return null; + + compositeKey.addKey(keyValue); + } + + return compositeKey; + } + + private static boolean addKey(OCompositeKey firstKey, List compositeKeys, boolean containsCollection, + Object keyValue) { + //in case of collection we split single composite key on several composite keys + //each of those composite keys contain single collection item. + //we can not contain more than single collection item in index + if (keyValue instanceof Collection) { + final Collection collectionKey = (Collection) keyValue; + final int collectionSize; + + //we insert null if collection is empty + if (collectionKey.isEmpty()) + collectionSize = 1; + else + collectionSize = collectionKey.size(); + + //if that is first collection we split single composite key on several keys, each of those + //composite keys contain single item from collection + if (!containsCollection) + //sure we need to expand collection only if collection size more than one, otherwise + //collection of composite keys already contains original composite key + for (int i = 1; i < collectionSize; i++) { + final OCompositeKey compositeKey = new OCompositeKey(firstKey.getKeys()); + compositeKeys.add(compositeKey); + } + else + throw new OIndexException("Composite key cannot contain more than one collection item"); + + int compositeIndex = 0; + if (!collectionKey.isEmpty()) { + for (final Object keyItem : collectionKey) { + final OCompositeKey compositeKey = compositeKeys.get(compositeIndex); + compositeKey.addKey(keyItem); + + compositeIndex++; + } + } else { + firstKey.addKey(null); + } + + containsCollection = true; + } else if (containsCollection) + for (final OCompositeKey compositeKey : compositeKeys) + compositeKey.addKey(keyValue); + else + firstKey.addKey(keyValue); + + return containsCollection; + } + + /** + * {@inheritDoc} + */ + public Object createValue(final Object... params) { + if (params.length == 1 && params[0] instanceof Collection) + return params[0]; + + return createValue(Arrays.asList(params)); + } + + public void processChangeEvent(OMultiValueChangeEvent changeEvent, Map keysToAdd, + Map keysToRemove, Object... params) { + + final OIndexDefinitionMultiValue indexDefinitionMultiValue = (OIndexDefinitionMultiValue) indexDefinitions + .get(multiValueDefinitionIndex); + + final CompositeWrapperMap compositeWrapperKeysToAdd = new CompositeWrapperMap(keysToAdd, indexDefinitions, params, + multiValueDefinitionIndex); + + final CompositeWrapperMap compositeWrapperKeysToRemove = new CompositeWrapperMap(keysToRemove, indexDefinitions, params, + multiValueDefinitionIndex); + + indexDefinitionMultiValue.processChangeEvent(changeEvent, compositeWrapperKeysToAdd, compositeWrapperKeysToRemove); + } + + /** + * {@inheritDoc} + */ + public int getParamCount() { + int total = 0; + for (final OIndexDefinition indexDefinition : indexDefinitions) + total += indexDefinition.getParamCount(); + return total; + } + + /** + * {@inheritDoc} + */ + public OType[] getTypes() { + final List types = new LinkedList(); + for (final OIndexDefinition indexDefinition : indexDefinitions) + Collections.addAll(types, indexDefinition.getTypes()); + + return types.toArray(new OType[types.size()]); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OCompositeIndexDefinition that = (OCompositeIndexDefinition) o; + + if (!className.equals(that.className)) + return false; + if (!indexDefinitions.equals(that.indexDefinitions)) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = indexDefinitions.hashCode(); + result = 31 * result + className.hashCode(); + return result; + } + + @Override + public String toString() { + return "OCompositeIndexDefinition{" + "indexDefinitions=" + indexDefinitions + ", className='" + className + '\'' + '}'; + } + + /** + * {@inheritDoc} + */ + @Override + public ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + serializeToStream(); + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + + return document; + } + + @Override + protected void serializeToStream() { + super.serializeToStream(); + + final List inds = new ArrayList(indexDefinitions.size()); + final List indClasses = new ArrayList(indexDefinitions.size()); + + document.field("className", className); + for (final OIndexDefinition indexDefinition : indexDefinitions) { + final ODocument indexDocument = indexDefinition.toStream(); + inds.add(indexDocument); + + indClasses.add(indexDefinition.getClass().getName()); + } + document.field("indexDefinitions", inds, OType.EMBEDDEDLIST); + document.field("indClasses", indClasses, OType.EMBEDDEDLIST); + document.field("nullValuesIgnored", isNullValuesIgnored()); + } + + /** + * {@inheritDoc} + */ + public String toCreateIndexDDL(final String indexName, final String indexType, String engine) { + final StringBuilder ddl = new StringBuilder("create index "); + ddl.append(indexName).append(" on ").append(className).append(" ( "); + + final Iterator fieldIterator = getFieldsToIndex().iterator(); + if (fieldIterator.hasNext()) { + ddl.append(fieldIterator.next()); + while (fieldIterator.hasNext()) { + ddl.append(", ").append(fieldIterator.next()); + } + } + ddl.append(" ) ").append(indexType).append(' '); + + if (engine != null) + ddl.append(OCommandExecutorSQLCreateIndex.KEYWORD_ENGINE + " " + engine).append(' '); + + if (multiValueDefinitionIndex == -1) { + boolean first = true; + for (OType oType : getTypes()) { + if (first) + first = false; + else + ddl.append(", "); + + ddl.append(oType.name()); + } + } + + return ddl.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void fromStream() { + serializeFromStream(); + } + + @Override + protected void serializeFromStream() { + super.serializeFromStream(); + + try { + className = document.field("className"); + + final List inds = document.field("indexDefinitions"); + final List indClasses = document.field("indClasses"); + + indexDefinitions.clear(); + + collate = new OCompositeCollate(this); + + for (int i = 0; i < indClasses.size(); i++) { + final Class clazz = Class.forName(indClasses.get(i)); + final ODocument indDoc = inds.get(i); + + final OIndexDefinition indexDefinition = (OIndexDefinition) clazz.getDeclaredConstructor().newInstance(); + indexDefinition.fromStream(indDoc); + + indexDefinitions.add(indexDefinition); + collate.addCollate(indexDefinition.getCollate()); + + if (indexDefinition instanceof OIndexDefinitionMultiValue) + multiValueDefinitionIndex = indexDefinitions.size() - 1; + } + + setNullValuesIgnored(!Boolean.FALSE.equals(document.field("nullValuesIgnored"))); + } catch (final ClassNotFoundException e) { + throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e); + } catch (final NoSuchMethodException e) { + throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e); + } catch (final InvocationTargetException e) { + throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e); + } catch (final InstantiationException e) { + throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e); + } catch (final IllegalAccessException e) { + throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e); + } + } + + @Override + public OCollate getCollate() { + return collate; + } + + @Override + public void setCollate(OCollate collate) { + throw new UnsupportedOperationException(); + } + + private static final class CompositeWrapperMap implements Map { + private final Map underlying; + private final Object[] params; + private final List indexDefinitions; + private final int multiValueIndex; + + private CompositeWrapperMap(Map underlying, List indexDefinitions, Object[] params, + int multiValueIndex) { + this.underlying = underlying; + this.params = params; + this.multiValueIndex = multiValueIndex; + this.indexDefinitions = indexDefinitions; + } + + public int size() { + return underlying.size(); + } + + public boolean isEmpty() { + return underlying.isEmpty(); + } + + public boolean containsKey(Object key) { + final OCompositeKey compositeKey = convertToCompositeKey(key); + + return underlying.containsKey(compositeKey); + } + + public boolean containsValue(Object value) { + return underlying.containsValue(value); + } + + public Integer get(Object key) { + return underlying.get(convertToCompositeKey(key)); + } + + public Integer put(Object key, Integer value) { + final OCompositeKey compositeKey = convertToCompositeKey(key); + return underlying.put(compositeKey, value); + } + + public Integer remove(Object key) { + return underlying.remove(convertToCompositeKey(key)); + } + + public void putAll(Map m) { + throw new UnsupportedOperationException("Unsupported because of performance reasons"); + } + + public void clear() { + underlying.clear(); + } + + public Set keySet() { + throw new UnsupportedOperationException("Unsupported because of performance reasons"); + } + + public Collection values() { + return underlying.values(); + } + + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + private OCompositeKey convertToCompositeKey(Object key) { + final OCompositeKey compositeKey = new OCompositeKey(); + + int paramsIndex = 0; + for (int i = 0; i < indexDefinitions.size(); i++) { + final OIndexDefinition indexDefinition = indexDefinitions.get(i); + if (i != multiValueIndex) { + compositeKey.addKey(indexDefinition.createValue(params[paramsIndex])); + paramsIndex++; + } else + compositeKey.addKey(((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(key)); + } + return compositeKey; + } + } + + @Override + public boolean isAutomatic() { + return indexDefinitions.get(0).isAutomatic(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeKey.java b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeKey.java new file mode 100755 index 00000000000..f5a1dc78981 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OCompositeKey.java @@ -0,0 +1,204 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.*; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Container for the list of heterogeneous values that are going to be stored in in index as composite keys. + * + * @author Andrey lomakin, Artem Orobets + * @see com.orientechnologies.orient.core.index.mvrbtree.OMVRBTree.PartialSearchMode + */ +@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED") +public class OCompositeKey implements Comparable, Serializable, ODocumentSerializable { + private static final long serialVersionUID = 1L; + /** + * List of heterogeneous values that are going to be stored in {@link com.orientechnologies.orient.core.index.mvrbtree.OMVRBTree}. + */ + private final List keys; + + private final transient Comparator comparator; + + public OCompositeKey(final List keys) { + this.keys = new ArrayList(keys.size()); + this.comparator = ODefaultComparator.INSTANCE; + + for (final Object key : keys) + addKey(key); + } + + public OCompositeKey(final Object... keys) { + this.keys = new ArrayList(keys.length); + this.comparator = ODefaultComparator.INSTANCE; + + for (final Object key : keys) + addKey(key); + } + + public OCompositeKey() { + this.keys = new ArrayList(); + this.comparator = ODefaultComparator.INSTANCE; + } + + /** + * Clears the keys array for reuse of the object + */ + public void reset() { + if (this.keys != null) + this.keys.clear(); + } + + /** + * @return List of heterogeneous values that are going to be stored in + * {@link com.orientechnologies.orient.core.index.mvrbtree.OMVRBTree}. + */ + public List getKeys() { + return Collections.unmodifiableList(keys); + } + + /** + * Add new key value to the list of already registered values. + *

      + * If passed in value is {@link OCompositeKey} itself then its values will be copied in current index. But key itself will not be + * added. + * + * @param key + * Key to add. + */ + public void addKey(final Object key) { + if (key instanceof OCompositeKey) { + final OCompositeKey compositeKey = (OCompositeKey) key; + for (final Object inKey : compositeKey.keys) { + addKey(inKey); + } + } else { + keys.add(key); + } + } + + /** + * Performs partial comparison of two composite keys. + *

      + * Two objects will be equal if the common subset of their keys is equal. For example if first object contains two keys and second + * contains four keys then only first two keys will be compared. + * + * @param otherKey + * Key to compare. + * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified + * object. + */ + public int compareTo(final OCompositeKey otherKey) { + final Iterator inIter = keys.iterator(); + final Iterator outIter = otherKey.keys.iterator(); + + while (inIter.hasNext() && outIter.hasNext()) { + final Object inKey = inIter.next(); + final Object outKey = outIter.next(); + + if (outKey instanceof OAlwaysGreaterKey) + return -1; + + if (outKey instanceof OAlwaysLessKey) + return 1; + + if (inKey instanceof OAlwaysGreaterKey) + return 1; + + if (inKey instanceof OAlwaysLessKey) + return -1; + + final int result = comparator.compare(inKey, outKey); + if (result != 0) + return result; + } + + return 0; + } + + /** + * {@inheritDoc } + */ + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OCompositeKey that = (OCompositeKey) o; + + return keys.equals(that.keys); + } + + /** + * {@inheritDoc } + */ + @Override + public int hashCode() { + return keys.hashCode(); + } + + /** + * {@inheritDoc } + */ + @Override + public String toString() { + return "OCompositeKey{" + "keys=" + keys + '}'; + } + + @Override + public ODocument toDocument() { + final ODocument document = new ODocument(); + for (int i = 0; i < keys.size(); i++) { + document.field("key" + i, keys.get(i)); + } + + return document; + } + + @Override + public void fromDocument(ODocument document) { + document.setLazyLoad(false); + + final String[] fieldNames = document.fieldNames(); + + final SortedMap keyMap = new TreeMap(); + + for (String fieldName : fieldNames) { + if (fieldName.startsWith("key")) { + final String keyIndex = fieldName.substring(3); + keyMap.put(Integer.valueOf(keyIndex), document.field(fieldName)); + } + } + + keys.clear(); + for (Object value : keyMap.values()) + keys.add(value); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/ODefaultIndexFactory.java b/core/src/main/java/com/orientechnologies/orient/core/index/ODefaultIndexFactory.java new file mode 100755 index 00000000000..e91612504fa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/ODefaultIndexFactory.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.index.engine.ORemoteIndexEngine; +import com.orientechnologies.orient.core.index.engine.OSBTreeIndexEngine; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Default OrientDB index factory for indexes based on SBTree.
      + * Supports index types: + *
        + *
      • UNIQUE
      • + *
      • NOTUNIQUE
      • + *
      • FULLTEXT
      • + *
      • DICTIONARY
      • + *
      + */ +public class ODefaultIndexFactory implements OIndexFactory { + + public static final String SBTREE_ALGORITHM = "SBTREE"; + + public static final String SBTREEBONSAI_VALUE_CONTAINER = "SBTREEBONSAISET"; + public static final String NONE_VALUE_CONTAINER = "NONE"; + + private static final Set TYPES; + private static final Set ALGORITHMS; + + static { + final Set types = new HashSet(); + types.add(OClass.INDEX_TYPE.UNIQUE.toString()); + types.add(OClass.INDEX_TYPE.NOTUNIQUE.toString()); + types.add(OClass.INDEX_TYPE.FULLTEXT.toString()); + types.add(OClass.INDEX_TYPE.DICTIONARY.toString()); + TYPES = Collections.unmodifiableSet(types); + } + + static { + final Set algorithms = new HashSet(); + algorithms.add(SBTREE_ALGORITHM); + ALGORITHMS = Collections.unmodifiableSet(algorithms); + } + + public static boolean isMultiValueIndex(final String indexType) { + switch (OClass.INDEX_TYPE.valueOf(indexType)) { + case UNIQUE: + case UNIQUE_HASH_INDEX: + case DICTIONARY: + case DICTIONARY_HASH_INDEX: + return false; + } + + return true; + } + + /** + * Index types : + *
        + *
      • UNIQUE
      • + *
      • NOTUNIQUE
      • + *
      • FULLTEXT
      • + *
      • DICTIONARY
      • + *
      + */ + public Set getTypes() { + return TYPES; + } + + public Set getAlgorithms() { + return ALGORITHMS; + } + + public OIndexInternal createIndex(String name, ODatabaseDocumentInternal database, String indexType, String algorithm, + String valueContainerAlgorithm, ODocument metadata, int version) throws OConfigurationException { + if (valueContainerAlgorithm == null) + valueContainerAlgorithm = NONE_VALUE_CONTAINER; + + if (version < 0) + version = getLastVersion(); + + if (SBTREE_ALGORITHM.equals(algorithm)) + return createSBTreeIndex(name, indexType, valueContainerAlgorithm, metadata, + (OAbstractPaginatedStorage) database.getStorage().getUnderlying(), version); + + throw new OConfigurationException("Unsupported type: " + indexType); + } + + private OIndexInternal createSBTreeIndex(String name, String indexType, String valueContainerAlgorithm, ODocument metadata, + OAbstractPaginatedStorage storage, int version) { + + if (OClass.INDEX_TYPE.UNIQUE.toString().equals(indexType)) { + return new OIndexUnique(name, indexType, SBTREE_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } else if (OClass.INDEX_TYPE.NOTUNIQUE.toString().equals(indexType)) { + return new OIndexNotUnique(name, indexType, SBTREE_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } else if (OClass.INDEX_TYPE.FULLTEXT.toString().equals(indexType)) { + return new OIndexFullText(name, indexType, SBTREE_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } else if (OClass.INDEX_TYPE.DICTIONARY.toString().equals(indexType)) { + return new OIndexDictionary(name, indexType, SBTREE_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } + + throw new OConfigurationException("Unsupported type: " + indexType); + } + + @Override + public int getLastVersion() { + return OSBTreeIndexEngine.VERSION; + } + + @Override + public OIndexEngine createIndexEngine(String algorithm, String name, Boolean durableInNonTxMode, OStorage storage, int version, + Map engineProperties) { + + final OIndexEngine indexEngine; + + final String storageType = storage.getType(); + if (storageType.equals("memory") || storageType.equals("plocal")) + indexEngine = new OSBTreeIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage, version); + else if (storageType.equals("distributed")) + // DISTRIBUTED CASE: HANDLE IT AS FOR LOCAL + indexEngine = new OSBTreeIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage.getUnderlying(), version); + else if (storageType.equals("remote")) + indexEngine = new ORemoteIndexEngine(name); + else + throw new OIndexException("Unsupported storage type: " + storageType); + + return indexEngine; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndex.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndex.java new file mode 100644 index 00000000000..d04fbd55a63 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndex.java @@ -0,0 +1,329 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.util.OApi; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Set; + +/** + * Basic interface to handle index. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public interface OIndex extends Comparable> { + String MERGE_KEYS = "mergeKeys"; + + /** + * Creates the index. + * + * @param name + * @param clusterIndexName Cluster name where to place the TreeMap + * @param clustersToIndex + * @param rebuild + * @param progressListener + */ + OIndex create(String name, OIndexDefinition indexDefinition, String clusterIndexName, Set clustersToIndex, + boolean rebuild, OProgressListener progressListener); + + String getDatabaseName(); + + /** + * Types of the keys that index can accept, if index contains composite key, list of types of elements from which this index + * consist will be returned, otherwise single element (key type obviously) will be returned. + */ + OType[] getKeyTypes(); + + /** + * Gets the set of records associated with the passed key. + * + * @param iKey The key to search + * @return The Record set if found, otherwise an empty Set + */ + T get(Object iKey); + + /** + * Tells if a key is contained in the index. + * + * @param iKey The key to search + * @return True if the key is contained, otherwise false + */ + boolean contains(Object iKey); + + /** + * Inserts a new entry in the index. The behaviour depends by the index implementation. + * + * @param iKey Entry's key + * @param iValue Entry's value as OIdentifiable instance + * @return The index instance itself to allow in chain calls + */ + OIndex put(Object iKey, OIdentifiable iValue); + + /** + * Removes an entry by its key. + * + * @param key The entry's key to remove + * @return True if the entry has been found and removed, otherwise false + */ + boolean remove(Object key); + + /** + * Removes an entry by its key and value. + * + * @param iKey The entry's key to remove + * @return True if the entry has been found and removed, otherwise false + */ + boolean remove(Object iKey, OIdentifiable iRID); + + /** + * Clears the index removing all the entries in one shot. + * + * @return The index instance itself to allow in chain calls + */ + OIndex clear(); + + /** + * @return number of entries in the index. + */ + long getSize(); + + /** + * Counts the entries for the key. + */ + long count(Object iKey); + + /** + * @return Number of keys in index + */ + long getKeySize(); + + /** + * For unique indexes it will throw exception if passed in key is contained in index. + * + * @param iRecord + * @param iKey + */ + ODocument checkEntry(OIdentifiable iRecord, Object iKey); + + /** + * Flushes in-memory changes to disk. + */ + public void flush(); + + /** + * Delete the index. + * + * @return The index instance itself to allow in chain calls + */ + @OApi(enduser = false) + OIndex delete(); + + /** + * Returns the index name. + * + * @return The name of the index + */ + String getName(); + + /** + * Returns the type of the index as string. + */ + String getType(); + + /** + * Returns the engine of the index as string. + */ + public String getAlgorithm(); + + /** + * Tells if the index is automatic. Automatic means it's maintained automatically by OrientDB. This is the case of indexes created + * against schema properties. Automatic indexes can always been rebuilt. + * + * @return True if the index is automatic, otherwise false + */ + boolean isAutomatic(); + + /** + * Rebuilds an automatic index. + * + * @return The number of entries rebuilt + * @see #getRebuildVersion() + */ + long rebuild(); + + /** + * Populate the index with all the existent records. + * + * @see #getRebuildVersion() + */ + long rebuild(OProgressListener iProgressListener); + + /** + * Returns the index configuration. + * + * @return An ODocument object containing all the index properties + */ + ODocument getConfiguration(); + + /** + * Returns binary format version for this index. + * Index format changes during system development but old formats are supported for binary compatibility. + * This method may be used to detect version of binary format which is used by current index and upgrade + * index to new one. + * + * @return Returns binary format version for this index if possible, otherwise -1. + */ + int getVersion(); + + /** + * Returns the internal index used. + */ + OIndexInternal getInternal(); + + /** + * Returns cursor which presents data associated with passed in keys. + * + * @param keys Keys data of which should be returned. + * @param ascSortOrder Flag which determines whether data iterated by cursor should be in ascending or descending order. + * @return cursor which presents data associated with passed in keys. + */ + OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder); + + OIndexDefinition getDefinition(); + + /** + * Returns Names of clusters that will be indexed. + * + * @return Names of clusters that will be indexed. + */ + Set getClusters(); + + /** + * Returns cursor which presents subset of index data between passed in keys. + * + * @param fromKey Lower border of index data. + * @param fromInclusive Indicates whether lower border should be inclusive or exclusive. + * @param toKey Upper border of index data. + * @param toInclusive Indicates whether upper border should be inclusive or exclusive. + * @param ascOrder Flag which determines whether data iterated by cursor should be in ascending or descending order. + * @return Cursor which presents subset of index data between passed in keys. + */ + OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, boolean ascOrder); + + /** + * Returns cursor which presents subset of data which associated with key which is greater than passed in key. + * + * @param fromKey Lower border of index data. + * @param fromInclusive Indicates whether lower border should be inclusive or exclusive. + * @param ascOrder Flag which determines whether data iterated by cursor should be in ascending or descending order. + * @return cursor which presents subset of data which associated with key which is greater than passed in key. + */ + OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder); + + /** + * Returns cursor which presents subset of data which associated with key which is less than passed in key. + * + * @param toKey Upper border of index data. + * @param toInclusive Indicates Indicates whether upper border should be inclusive or exclusive. + * @param ascOrder Flag which determines whether data iterated by cursor should be in ascending or descending order. + * @return cursor which presents subset of data which associated with key which is less than passed in key. + */ + OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder); + + OIndexCursor cursor(); + + OIndexCursor descCursor(); + + OIndexKeyCursor keyCursor(); + + ODocument getMetadata(); + + boolean supportsOrderedIterations(); + + /** + * Returns amount of times when index was rebuilt since storage was opened. + *

      + * It is used to support so called "live index rebuild" feature. + *

      + * Value of this version is increased every time when index is going to be rebuild. + * So if two sequential calls of this method return different numbers it means that index at least started to rebuild + * itself. + *

      + * If you use indexes to increase speed of fetching data from database you should follow following workflow: + *

        + *
      1. Read index rebuild version.
      2. + *
      3. Check index rebuild flag {@link #isRebuilding()}, if it is true, do not use index and fetch data directly from + * database clusters.
      4. + *
      5. Fetch data from index.
      6. + *
      7. Read index rebuild version again, if it is not equal to version which was read at first time, go to step 2. + * It is VERY important do not reorder steps 1 and 2. + * Such recording may lead to situation when index is rebuilding but we miss this state.
      8. + *
      + *

      + * This approach works well ONLY if you do not use methods which return {@link OIndexCursor} instance. + * In case of you work with cursors index rebuild may cause data inconsistency issues in both: + *

        + *
      1. Code which calls index methods to create cursor (can be avoided using steps are listed above)
      2. + *
      3. During iteration over the cursor itself
      4. + *
      + *

      + * To detect last data inconsistency issue please use cursor wrapper + * {@link OIndexChangesWrapper} which throws {@link com.orientechnologies.orient.core.exception.OIndexIsRebuildingException} + * in case of index rebuild. + *

      + * Both of these approaches are used in implementation of support of "live index rebuild" for SELECT SQL queries. + *

        + *
      1. In case of index which we are going to use to speed up SELECT query is rebuilding + * we skip this index.
      2. + *
      3. If index is rebuilding at the moment when we iterate over the cursor + * we catch {@link com.orientechnologies.orient.core.exception.OIndexIsRebuildingException} exception + * and retry whole query again.
      4. + *
      + * + * @return amount of times when index was rebuilt since the start of the storage. + * @see com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#searchForIndexes(com.orientechnologies.orient.core.metadata.schema.OClass) + * @see com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#getIndexCursors(com.orientechnologies.orient.core.metadata.schema.OClass) + * @see com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#getOptimizedSortCursor(com.orientechnologies.orient.core.metadata.schema.OClass) + * @see com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage#command(com.orientechnologies.orient.core.command.OCommandRequestText) + * @see OIndexChangesWrapper + * @see com.orientechnologies.orient.core.exception.OIndexIsRebuildingException + * @see com.orientechnologies.orient.core.exception.ORetryQueryException + */ + long getRebuildVersion(); + + /** + * @return Indicates whether index is rebuilding at the moment. + * @see #getRebuildVersion() + */ + boolean isRebuilding(); + + Object getFirstKey(); + + Object getLastKey(); + + int getIndexId(); + + boolean isUnique(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstract.java new file mode 100644 index 00000000000..44e38b3abe8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstract.java @@ -0,0 +1,1184 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.concur.lock.*; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainer; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.exception.OTooBigIndexKeyException; +import com.orientechnologies.orient.core.intent.OIntentMassiveInsert; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.cache.OReadCache; +import com.orientechnologies.orient.core.storage.cache.OWriteCache; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.OIndexEngineCallback; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; + +/** + * Handles indexing when records change. The underlying lock manager for keys can be the {@link OPartitionedLockManager}, the + * default one, or the {@link OOneEntryPerKeyLockManager} in case of distributed. This is to avoid deadlock situation between nodes + * where keys have the same hash code. + * + * @author Luca Garulli + */ +public abstract class OIndexAbstract implements OIndexInternal, OOrientStartupListener, OOrientShutdownListener { + + protected static final String CONFIG_MAP_RID = "mapRid"; + protected static final String CONFIG_CLUSTERS = "clusters"; + protected final String type; + protected final OLockManager keyLockManager; + protected volatile IndexConfiguration configuration; + + protected final ODocument metadata; + protected final OAbstractPaginatedStorage storage; + private final String databaseName; + private final String name; + + private final OReadersWriterSpinLock rwLock = new OReadersWriterSpinLock(); + private final AtomicLong rebuildVersion = new AtomicLong(); + + private final int version; + protected String valueContainerAlgorithm; + + protected volatile int indexId = -1; + + private String algorithm; + private Set clustersToIndex = new HashSet(); + private volatile OIndexDefinition indexDefinition; + private volatile boolean rebuilding = false; + private volatile ThreadLocal txSnapshot = new IndexTxSnapshotThreadLocal(); + private Map engineProperties = new HashMap(); + + public OIndexAbstract(String name, final String type, final String algorithm, final String valueContainerAlgorithm, + final ODocument metadata, final int version, final OStorage storage) { + acquireExclusiveLock(); + try { + databaseName = ODatabaseRecordThreadLocal.INSTANCE.get().getName(); + + this.version = version; + this.name = name; + this.type = type; + this.algorithm = algorithm; + this.metadata = metadata; + this.valueContainerAlgorithm = valueContainerAlgorithm; + this.storage = (OAbstractPaginatedStorage) storage.getUnderlying(); + this.keyLockManager = Orient.instance().isRunningDistributed() ? + new OIndexOneEntryPerKeyLockManager() : + new OPartitionedLockManager(); + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } finally { + releaseExclusiveLock(); + } + } + + public static OIndexMetadata loadMetadataInternal(final ODocument config, final String type, final String algorithm, + final String valueContainerAlgorithm) { + final String indexName = config.field(OIndexInternal.CONFIG_NAME); + + final ODocument indexDefinitionDoc = config.field(OIndexInternal.INDEX_DEFINITION); + OIndexDefinition loadedIndexDefinition = null; + if (indexDefinitionDoc != null) { + try { + final String indexDefClassName = config.field(OIndexInternal.INDEX_DEFINITION_CLASS); + final Class indexDefClass = Class.forName(indexDefClassName); + loadedIndexDefinition = (OIndexDefinition) indexDefClass.getDeclaredConstructor().newInstance(); + loadedIndexDefinition.fromStream(indexDefinitionDoc); + + } catch (final ClassNotFoundException e) { + throw OException.wrapException(new OIndexException("Error during deserialization of index definition"), e); + } catch (final NoSuchMethodException e) { + throw OException.wrapException(new OIndexException("Error during deserialization of index definition"), e); + } catch (final InvocationTargetException e) { + throw OException.wrapException(new OIndexException("Error during deserialization of index definition"), e); + } catch (final InstantiationException e) { + throw OException.wrapException(new OIndexException("Error during deserialization of index definition"), e); + } catch (final IllegalAccessException e) { + throw OException.wrapException(new OIndexException("Error during deserialization of index definition"), e); + } + } else { + // @COMPATIBILITY 1.0rc6 new index model was implemented + final Boolean isAutomatic = config.field(OIndexInternal.CONFIG_AUTOMATIC); + OIndexFactory factory = OIndexes.getFactory(type, algorithm); + if (Boolean.TRUE.equals(isAutomatic)) { + final int pos = indexName.lastIndexOf('.'); + if (pos < 0) + throw new OIndexException( + "Cannot convert from old index model to new one. " + "Invalid index name. Dot (.) separator should be present"); + final String className = indexName.substring(0, pos); + final String propertyName = indexName.substring(pos + 1); + + final String keyTypeStr = config.field(OIndexInternal.CONFIG_KEYTYPE); + if (keyTypeStr == null) + throw new OIndexException("Cannot convert from old index model to new one. " + "Index key type is absent"); + final OType keyType = OType.valueOf(keyTypeStr.toUpperCase(Locale.ENGLISH)); + + loadedIndexDefinition = new OPropertyIndexDefinition(className, propertyName, keyType); + + config.removeField(OIndexInternal.CONFIG_AUTOMATIC); + config.removeField(OIndexInternal.CONFIG_KEYTYPE); + } else if (config.field(OIndexInternal.CONFIG_KEYTYPE) != null) { + final String keyTypeStr = config.field(OIndexInternal.CONFIG_KEYTYPE); + final OType keyType = OType.valueOf(keyTypeStr.toUpperCase(Locale.ENGLISH)); + + loadedIndexDefinition = new OSimpleKeyIndexDefinition(factory.getLastVersion(), keyType); + + config.removeField(OIndexInternal.CONFIG_KEYTYPE); + } + } + + final Set clusters = new HashSet((Collection) config.field(CONFIG_CLUSTERS, OType.EMBEDDEDSET)); + + return new OIndexMetadata(indexName, loadedIndexDefinition, clusters, type, algorithm, valueContainerAlgorithm); + } + + @Override + public void onShutdown() { + txSnapshot = null; + } + + @Override + public void onStartup() { + if (txSnapshot == null) + txSnapshot = new IndexTxSnapshotThreadLocal(); + } + + public void flush() { + } + + @Override + public boolean hasRangeQuerySupport() { + + acquireSharedLock(); + try { + while (true) + try { + return storage.hasIndexRangeQuerySupport(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + + } + + /** + * Creates the index. + * + * @param clusterIndexName Cluster name where to place the TreeMap + * @param clustersToIndex + * @param rebuild + * @param progressListener + * @param valueSerializer + */ + public OIndexInternal create(final OIndexDefinition indexDefinition, final String clusterIndexName, + final Set clustersToIndex, boolean rebuild, final OProgressListener progressListener, + final OBinarySerializer valueSerializer) { + acquireExclusiveLock(); + try { + configuration = indexConfigurationInstance(new ODocument().setTrackingChanges(false)); + + this.indexDefinition = indexDefinition; + + if (clustersToIndex != null) + this.clustersToIndex = new HashSet(clustersToIndex); + else + this.clustersToIndex = new HashSet(); + + // do not remove this, it is needed to remove index garbage if such one exists + try { + removeValuesContainer(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during deletion of index '%s'", name); + } + + final Boolean durableInNonTxMode = isDurableInNonTxMode(); + + indexId = storage + .addIndexEngine(name, algorithm, type, indexDefinition, valueSerializer, isAutomatic(), durableInNonTxMode, version, + getEngineProperties(), clustersToIndex, metadata); + assert indexId >= 0; + + onIndexEngineChange(indexId); + + if (rebuild) + fillIndex(progressListener); + + updateConfiguration(); + } catch (Exception e) { + OLogManager.instance().error(this, "Exception during index '%s' creation", e, name); + + while (true) + try { + if (indexId >= 0) + storage.deleteIndexEngine(indexId); + break; + } catch (OInvalidIndexEngineIdException ex) { + doReloadIndexEngine(); + } catch (Exception ex) { + OLogManager.instance().error(this, "Exception during index '%s' deletion", ex, name); + } + + if (e instanceof OIndexException) + throw (OIndexException) e; + + throw OException.wrapException(new OIndexException("Cannot create the index '" + name + "'"), e); + + } finally { + releaseExclusiveLock(); + } + + return this; + } + + protected void doReloadIndexEngine() { + indexId = storage.loadIndexEngine(name); + + if (indexId < 0) { + throw new IllegalStateException("Index " + name + " can not be loaded"); + } + } + + public long count(final Object iKey) { + final Object result = get(iKey); + if (result == null) + return 0; + else if (OMultiValue.isMultiValue(result)) + return OMultiValue.getSize(result); + return 1; + } + + private Boolean isDurableInNonTxMode() { + Boolean durableInNonTxMode; + + Object durable = null; + + if (metadata != null) { + durable = metadata.field("durableInNonTxMode"); + } + + if (durable instanceof Boolean) + durableInNonTxMode = (Boolean) durable; + else + durableInNonTxMode = null; + return durableInNonTxMode; + } + + public boolean loadFromConfiguration(final ODocument config) { + acquireExclusiveLock(); + try { + configuration = indexConfigurationInstance(config); + clustersToIndex.clear(); + + final OIndexMetadata indexMetadata = loadMetadata(config); + indexDefinition = indexMetadata.getIndexDefinition(); + clustersToIndex.addAll(indexMetadata.getClustersToIndex()); + algorithm = indexMetadata.getAlgorithm(); + valueContainerAlgorithm = indexMetadata.getValueContainerAlgorithm(); + + try { + indexId = storage.loadIndexEngine(name); + + if (indexId == -1) { + indexId = storage + .loadExternalIndexEngine(name, algorithm, type, indexDefinition, determineValueSerializer(), isAutomatic(), + isDurableInNonTxMode(), version, getEngineProperties()); + } + + if (indexId == -1) + return false; + + onIndexEngineChange(indexId); + + } catch (Exception e) { + OLogManager.instance().error(this, "Error during load of index '%s'", e, name != null ? name : "null"); + + if (isAutomatic()) { + // AUTOMATIC REBUILD IT + OLogManager.instance().warn(this, "Cannot load index '%s' rebuilt it from scratch", getName()); + try { + rebuild(); + } catch (Throwable t) { + OLogManager.instance() + .error(this, "Cannot rebuild index '%s' because '" + t + "'. The index will be removed in configuration", e, + getName()); + // REMOVE IT + return false; + } + } + } + + return true; + } finally { + releaseExclusiveLock(); + } + } + + protected Map getEngineProperties() { + return engineProperties; + } + + @Override + public OIndexMetadata loadMetadata(final ODocument config) { + return loadMetadataInternal(config, type, algorithm, valueContainerAlgorithm); + } + + public boolean contains(Object key) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireSharedLock(key); + try { + acquireSharedLock(); + try { + assert indexId >= 0; + + while (true) + try { + return storage.indexContainsKey(indexId, key); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(key); + } + } + + /** + * {@inheritDoc} + */ + public long rebuild() { + return rebuild(new OIndexRebuildOutputListener(this)); + } + + @Override + public void setRebuildingFlag() { + rebuilding = true; + } + + @Override + public void close() { + + } + + @Override + public Object getFirstKey() { + acquireSharedLock(); + try { + while (true) + try { + return storage.getIndexFirstKey(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + @Override + public Object getLastKey() { + acquireSharedLock(); + try { + while (true) + try { + return storage.getIndexLastKey(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public long getRebuildVersion() { + return 0; + } + + /** + * {@inheritDoc} + */ + public long rebuild(final OProgressListener iProgressListener) { + long documentIndexed = 0; + + final boolean intentInstalled = getDatabase().declareIntent(new OIntentMassiveInsert()); + + acquireExclusiveLock(); + try { + // DO NOT REORDER 2 assignments bellow + // see #getRebuildVersion() + rebuilding = true; + rebuildVersion.incrementAndGet(); + + try { + if (indexId >= 0) + storage.deleteIndexEngine(indexId); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during index '%s' delete", name); + } + + removeValuesContainer(); + + indexId = storage + .addIndexEngine(name, algorithm, type, indexDefinition, determineValueSerializer(), isAutomatic(), isDurableInNonTxMode(), + version, getEngineProperties(), clustersToIndex, metadata); + + onIndexEngineChange(indexId); + } catch (Exception e) { + try { + if (indexId >= 0) + storage.clearIndex(indexId); + } catch (Exception e2) { + OLogManager.instance().error(this, "Error during index rebuild", e2); + // IGNORE EXCEPTION: IF THE REBUILD WAS LAUNCHED IN CASE OF RID INVALID CLEAR ALWAYS GOES IN ERROR + } + + rebuilding = false; + throw OException.wrapException(new OIndexException("Error on rebuilding the index for clusters: " + clustersToIndex), e); + } finally { + releaseExclusiveLock(); + } + + acquireSharedLock(); + try { + documentIndexed = fillIndex(iProgressListener); + } catch (final Exception e) { + OLogManager.instance().error(this, "Error during index rebuild", e); + + try { + if (indexId >= 0) + storage.clearIndex(indexId); + } catch (Exception e2) { + OLogManager.instance().error(this, "Error during index rebuild", e2); + // IGNORE EXCEPTION: IF THE REBUILD WAS LAUNCHED IN CASE OF RID INVALID CLEAR ALWAYS GOES IN ERROR + } + + throw OException.wrapException(new OIndexException("Error on rebuilding the index for clusters: " + clustersToIndex), e); + } finally { + rebuilding = false; + + if (intentInstalled) + getDatabase().declareIntent(null); + + releaseSharedLock(); + } + + return documentIndexed; + } + + private long fillIndex(OProgressListener iProgressListener) { + long documentIndexed = 0; + try { + long documentNum = 0; + long documentTotal = 0; + + for (final String cluster : clustersToIndex) + documentTotal += getDatabase().countClusterElements(cluster); + + if (iProgressListener != null) + iProgressListener.onBegin(this, documentTotal, true); + + // INDEX ALL CLUSTERS + for (final String clusterName : clustersToIndex) { + final long[] metrics = indexCluster(clusterName, iProgressListener, documentNum, documentIndexed, documentTotal); + documentNum = metrics[0]; + documentIndexed = metrics[1]; + } + + if (iProgressListener != null) + iProgressListener.onCompletition(this, true); + } catch (final RuntimeException e) { + if (iProgressListener != null) + iProgressListener.onCompletition(this, false); + throw e; + } + return documentIndexed; + } + + public boolean remove(Object key, final OIdentifiable value) { + return remove(key); + } + + public boolean remove(Object key) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireExclusiveLock(key); + try { + acquireSharedLock(); + try { + while (true) + try { + return storage.removeKeyFromIndex(indexId, key); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + @Override + public void lockKeysForUpdate(Object... key) { + if (key == null || key.length == 0) + return; + + keyLockManager.acquireExclusiveLocksInBatch(key); + } + + @Override + public Lock[] lockKeysForUpdate(final Collection keys) { + if (keys == null || keys.isEmpty()) + return new Lock[0]; + + return keyLockManager.acquireExclusiveLocksInBatch(keys); + } + + @Override + public void releaseKeysForUpdate(Object... key) { + if (key == null || key.length == 0) + return; + + for (Object k : key) + keyLockManager.releaseExclusiveLock(k); + } + + public OIndex clear() { + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.lockAllExclusive(); + + try { + + acquireSharedLock(); + try { + while (true) + try { + storage.clearIndex(indexId); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + return this; + } finally { + releaseSharedLock(); + } + + } finally { + if (!txIsActive) + keyLockManager.unlockAllExclusive(); + } + } + + public OIndexInternal delete() { + acquireExclusiveLock(); + + try { + while (true) + try { + storage.deleteIndexEngine(indexId); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + + // REMOVE THE INDEX ALSO FROM CLASS MAP + if (getDatabase().getMetadata() != null) + getDatabase().getMetadata().getIndexManager().removeClassPropertyIndex(this); + + removeValuesContainer(); + return this; + } finally { + releaseExclusiveLock(); + } + } + + public String getName() { + acquireSharedLock(); + try { + return name; + } finally { + releaseSharedLock(); + } + } + + public String getType() { + acquireSharedLock(); + try { + return type; + } finally { + releaseSharedLock(); + } + } + + @Override + public String getAlgorithm() { + acquireSharedLock(); + try { + return algorithm; + } finally { + releaseSharedLock(); + } + } + + @Override + public String toString() { + acquireSharedLock(); + try { + return name; + } finally { + releaseSharedLock(); + } + } + + public OIndexInternal getInternal() { + return this; + } + + public Set getClusters() { + acquireSharedLock(); + try { + return Collections.unmodifiableSet(clustersToIndex); + } finally { + releaseSharedLock(); + } + } + + public OIndexAbstract addCluster(final String clusterName) { + acquireExclusiveLock(); + try { + if (clustersToIndex.add(clusterName)) { + updateConfiguration(); + + // INDEX SINGLE CLUSTER + indexCluster(clusterName, null, 0, 0, 0); + } + + return this; + } finally { + releaseExclusiveLock(); + } + } + + public OIndexAbstract removeCluster(String iClusterName) { + acquireExclusiveLock(); + try { + if (clustersToIndex.remove(iClusterName)) { + updateConfiguration(); + rebuild(); + } + + return this; + } finally { + releaseExclusiveLock(); + } + } + + public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) { + return null; + } + + public ODocument updateConfiguration() { + configuration.updateConfiguration(type, name, version, indexDefinition, clustersToIndex, algorithm, valueContainerAlgorithm); + if (metadata != null) + configuration.document.field(OIndexInternal.METADATA, metadata, OType.EMBEDDED); + return configuration.getDocument(); + } + + public void addTxOperation(final OTransactionIndexChanges changes) { + acquireSharedLock(); + try { + final IndexTxSnapshot indexTxSnapshot = txSnapshot.get(); + if (changes.cleared) + clearSnapshot(indexTxSnapshot); + final Map snapshot = indexTxSnapshot.indexSnapshot; + for (final OTransactionIndexChangesPerKey entry : changes.changesPerKey.values()) { + applyIndexTxEntry(snapshot, entry); + } + applyIndexTxEntry(snapshot, changes.nullKeyChanges); + + } finally { + releaseSharedLock(); + } + } + + /** + * Interprets transaction index changes for a certain key. Override it to customize index behaviour on interpreting index changes. + * This may be viewed as an optimization, but in some cases this is a requirement. For example, if you put multiple values under + * the same key during the transaction for single-valued/unique index, but remove all of them except one before commit, there is + * no point in throwing {@link com.orientechnologies.orient.core.storage.ORecordDuplicatedException} while applying index changes. + * + * @param changes the changes to interpret. + * + * @return the interpreted index key changes. + */ + protected Iterable interpretTxKeyChanges( + OTransactionIndexChangesPerKey changes) { + return changes.entries; + } + + private void applyIndexTxEntry(Map snapshot, OTransactionIndexChangesPerKey entry) { + for (OTransactionIndexChangesPerKey.OTransactionIndexEntry op : interpretTxKeyChanges(entry)) { + switch (op.operation) { + case PUT: + putInSnapshot(entry.key, op.value, snapshot); + break; + case REMOVE: + if (op.value != null) + removeFromSnapshot(entry.key, op.value, snapshot); + else + removeFromSnapshot(entry.key, snapshot); + break; + case CLEAR: + // SHOULD NEVER BE THE CASE HANDLE BY cleared FLAG + break; + } + } + } + + public void commit() { + acquireSharedLock(); + try { + final IndexTxSnapshot indexTxSnapshot = txSnapshot.get(); + if (indexTxSnapshot.clear) + clear(); + + commitSnapshot(indexTxSnapshot.indexSnapshot); + } finally { + releaseSharedLock(); + } + } + + public void preCommit() { + txSnapshot.set(new IndexTxSnapshot()); + } + + public void postCommit() { + txSnapshot.set(new IndexTxSnapshot()); + } + + public ODocument getConfiguration() { + return configuration.getDocument(); + } + + @Override + public int getVersion() { + final IndexConfiguration conf = this.configuration; + return version; + } + + @Override + public ODocument getMetadata() { + return metadata; + } + + @Override + public boolean isUnique() { + return false; + } + + public boolean isAutomatic() { + acquireSharedLock(); + try { + return indexDefinition != null && indexDefinition.getClassName() != null; + } finally { + releaseSharedLock(); + } + } + + public OType[] getKeyTypes() { + acquireSharedLock(); + try { + if (indexDefinition == null) + return null; + + return indexDefinition.getTypes(); + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexKeyCursor keyCursor() { + acquireSharedLock(); + try { + while (true) + try { + return storage.getIndexKeyCursor(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + public OIndexDefinition getDefinition() { + return indexDefinition; + } + + @Override + public boolean equals(final Object o) { + acquireSharedLock(); + try { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OIndexAbstract that = (OIndexAbstract) o; + + if (!name.equals(that.name)) + return false; + + return true; + } finally { + releaseSharedLock(); + } + } + + @Override + public int hashCode() { + acquireSharedLock(); + try { + return name.hashCode(); + } finally { + releaseSharedLock(); + } + } + + public int getIndexId() { + return indexId; + } + + public String getDatabaseName() { + return databaseName; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRebuilding() { + return rebuilding; + } + + protected abstract OBinarySerializer determineValueSerializer(); + + protected void populateIndex(ODocument doc, Object fieldValue) { + if (fieldValue instanceof Collection) { + for (final Object fieldValueItem : (Collection) fieldValue) { + put(fieldValueItem, doc); + } + } else + put(fieldValue, doc); + } + + public Object getCollatingValue(final Object key) { + if (key != null && getDefinition() != null) + return getDefinition().getCollate().transform(key); + return key; + } + + protected void commitSnapshot(Map snapshot) { + // do nothing by default + // storage will delay real operations till the end of tx + } + + protected void putInSnapshot(Object key, OIdentifiable value, Map snapshot) { + // storage will delay real operations till the end of tx + put(key, value); + } + + protected void removeFromSnapshot(Object key, OIdentifiable value, Map snapshot) { + // storage will delay real operations till the end of tx + remove(key, value); + } + + protected void removeFromSnapshot(Object key, Map snapshot) { + // storage will delay real operations till the end of tx + remove(key); + } + + protected void clearSnapshot(IndexTxSnapshot indexTxSnapshot) { + // storage will delay real operations till the end of tx + clear(); + } + + @Override + public int compareTo(OIndex index) { + acquireSharedLock(); + try { + final String name = index.getName(); + return this.name.compareTo(name); + } finally { + releaseSharedLock(); + } + } + + @Override + public void setType(OType type) { + indexDefinition = new OSimpleKeyIndexDefinition(version, type); + updateConfiguration(); + } + + @Override + public String getIndexNameByKey(final Object key) { + OIndexEngine engine; + while (true) + try { + engine = storage.getIndexEngine(indexId); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + return engine.getIndexNameByKey(key); + } + + @Override + public boolean acquireAtomicExclusiveLock(Object key) { + OIndexEngine engine; + while (true) + try { + engine = storage.getIndexEngine(indexId); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + + return engine.acquireAtomicExclusiveLock(key); + } + + protected ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + protected long[] indexCluster(final String clusterName, final OProgressListener iProgressListener, long documentNum, + long documentIndexed, long documentTotal) { + try { + for (final ORecord record : getDatabase().browseCluster(clusterName)) { + if (Thread.interrupted()) + throw new OCommandExecutionException("The index rebuild has been interrupted"); + + if (record instanceof ODocument) { + final ODocument doc = (ODocument) record; + + if (indexDefinition == null) + throw new OConfigurationException( + "Index '" + name + "' cannot be rebuilt because has no a valid definition (" + indexDefinition + ")"); + + final Object fieldValue = indexDefinition.getDocumentValueToIndex(doc); + + if (fieldValue != null || !indexDefinition.isNullValuesIgnored()) { + try { + populateIndex(doc, fieldValue); + } catch (OTooBigIndexKeyException e) { + OLogManager.instance().error(this, + "Exception during index rebuild. Exception was caused by following key/ value pair - key %s, value %s." + + " Rebuild will continue from this point", e, fieldValue, doc.getIdentity()); + } catch (OIndexException e) { + OLogManager.instance().error(this, + "Exception during index rebuild. Exception was caused by following key/ value pair - key %s, value %s." + + " Rebuild will continue from this point", e, fieldValue, doc.getIdentity()); + } + + ++documentIndexed; + } + } + documentNum++; + + if (iProgressListener != null) + iProgressListener.onProgress(this, documentNum, (float) (documentNum * 100.0 / documentTotal)); + } + } catch (NoSuchElementException e) { + // END OF CLUSTER REACHED, IGNORE IT + } + + return new long[] { documentNum, documentIndexed }; + } + + protected void releaseExclusiveLock() { + rwLock.releaseWriteLock(); + } + + protected void acquireExclusiveLock() { + rwLock.acquireWriteLock(); + } + + protected void releaseSharedLock() { + rwLock.releaseReadLock(); + } + + protected void acquireSharedLock() { + rwLock.acquireReadLock(); + } + + private void removeValuesContainer() { + if (valueContainerAlgorithm.equals(ODefaultIndexFactory.SBTREEBONSAI_VALUE_CONTAINER)) { + + final OAtomicOperation atomicOperation = storage.getAtomicOperationsManager().getCurrentOperation(); + + final OReadCache readCache = storage.getReadCache(); + final OWriteCache writeCache = storage.getWriteCache(); + + if (atomicOperation == null) { + try { + final String fileName = getName() + OIndexRIDContainer.INDEX_FILE_EXTENSION; + if (writeCache.exists(fileName)) { + final long fileId = writeCache.loadFile(fileName); + readCache.deleteFile(fileId, writeCache); + } + } catch (IOException e) { + OLogManager.instance().error(this, "Cannot delete file for value containers", e); + } + } else { + try { + final String fileName = getName() + OIndexRIDContainer.INDEX_FILE_EXTENSION; + if (atomicOperation.isFileExists(fileName)) { + final long fileId = atomicOperation.loadFile(fileName); + atomicOperation.deleteFile(fileId); + } + } catch (IOException e) { + OLogManager.instance().error(this, "Cannot delete file for value containers", e); + } + } + + } + } + + protected void onIndexEngineChange(final int indexId) { + while (true) + try { + storage.callIndexEngine(false, false, indexId, new OIndexEngineCallback() { + @Override + public Object callEngine(OIndexEngine engine) { + engine.init(getName(), getType(), getDefinition(), isAutomatic(), getMetadata()); + return null; + } + }); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + protected static final class IndexTxSnapshot { + public Map indexSnapshot = new HashMap(); + public boolean clear = false; + } + + private static class IndexTxSnapshotThreadLocal extends ThreadLocal { + @Override + protected IndexTxSnapshot initialValue() { + return new IndexTxSnapshot(); + } + } + + protected IndexConfiguration indexConfigurationInstance(final ODocument document) { + return new IndexConfiguration(document); + } + + protected static class IndexConfiguration { + protected final ODocument document; + + public IndexConfiguration(ODocument document) { + this.document = document; + } + + public ODocument getDocument() { + return document; + } + + public synchronized ODocument updateConfiguration(String type, String name, int version, OIndexDefinition indexDefinition, + Set clustersToIndex, String algorithm, String valueContainerAlgorithm) { + document.field(OIndexInternal.CONFIG_TYPE, type); + document.field(OIndexInternal.CONFIG_NAME, name); + document.field(OIndexInternal.INDEX_VERSION, version); + + if (indexDefinition != null) { + + final ODocument indexDefDocument = indexDefinition.toStream(); + if (!indexDefDocument.hasOwners()) + ODocumentInternal.addOwner(indexDefDocument, document); + + document.field(OIndexInternal.INDEX_DEFINITION, indexDefDocument, OType.EMBEDDED); + document.field(OIndexInternal.INDEX_DEFINITION_CLASS, indexDefinition.getClass().getName()); + } else { + document.removeField(OIndexInternal.INDEX_DEFINITION); + document.removeField(OIndexInternal.INDEX_DEFINITION_CLASS); + } + + document.field(CONFIG_CLUSTERS, clustersToIndex, OType.EMBEDDEDSET); + document.field(ALGORITHM, algorithm); + document.field(VALUE_CONTAINER_ALGORITHM, valueContainerAlgorithm); + + return document; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractCursor.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractCursor.java new file mode 100644 index 00000000000..45423558147 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractCursor.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.HashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/24/14 + */ +public abstract class OIndexAbstractCursor implements OIndexCursor { + protected int prefetchSize = -1; + private Map.Entry nextEntry; + private boolean firstTime = true; + + @Override + public Set toValues() { + final HashSet result = new HashSet(); + Map.Entry entry = nextEntry(); + + while (entry != null) { + result.add(entry.getValue()); + entry = nextEntry(); + } + + return result; + } + + @Override + public Set> toEntries() { + final HashSet> result = new HashSet>(); + + Map.Entry entry = nextEntry(); + + while (entry != null) { + result.add(entry); + entry = nextEntry(); + } + + return result; + } + + @Override + public Set toKeys() { + final HashSet result = new HashSet(); + + Map.Entry entry = nextEntry(); + + while (entry != null) { + result.add(entry.getKey()); + entry = nextEntry(); + } + + return result; + } + + @Override + public boolean hasNext() { + if (firstTime) { + nextEntry = nextEntry(); + firstTime = false; + } + + return nextEntry != null; + + } + + @Override + public OIdentifiable next() { + if (!hasNext()) + throw new NoSuchElementException(); + + final Map.Entry result = nextEntry; + nextEntry = nextEntry(); + + return result.getValue(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + public int getPrefetchSize() { + return prefetchSize; + } + + public void setPrefetchSize(final int prefetchSize) { + this.prefetchSize = prefetchSize; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractDelegate.java new file mode 100644 index 00000000000..e3adfa84165 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexAbstractDelegate.java @@ -0,0 +1,277 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Set; + +/** + * Generic abstract wrapper for indexes. It delegates all the operations to the wrapped OIndex instance. + * + * @author Luca Garulli + */ +public class OIndexAbstractDelegate implements OIndex { + protected OIndex delegate; + + public OIndexAbstractDelegate(final OIndex iDelegate) { + this.delegate = iDelegate; + } + + @SuppressWarnings("unchecked") + public OIndexInternal getInternal() { + OIndex internal = delegate; + while (!(internal instanceof OIndexInternal) && internal != null) + internal = internal.getInternal(); + + return (OIndexInternal) internal; + } + + public OIndex create(final String name, final OIndexDefinition indexDefinition, final String clusterIndexName, + final Set clustersToIndex, boolean rebuild, final OProgressListener progressListener) { + return delegate.create(name, indexDefinition, clusterIndexName, clustersToIndex, rebuild, progressListener); + } + + public T get(final Object iKey) { + return delegate.get(iKey); + } + + public boolean contains(final Object iKey) { + return delegate.contains(iKey); + } + + public OIndex put(final Object iKey, final OIdentifiable iValue) { + checkForKeyType(iKey); + return delegate.put(iKey, iValue); + } + + @Override + public long getRebuildVersion() { + return delegate.getRebuildVersion(); + } + + public boolean remove(final Object key) { + return delegate.remove(key); + } + + public boolean remove(final Object iKey, final OIdentifiable iRID) { + return delegate.remove(iKey, iRID); + } + + public OIndex clear() { + return delegate.clear(); + } + + protected void checkForKeyType(final Object iKey) { + if (delegate.getDefinition() == null) { + // RECOGNIZE THE KEY TYPE AT RUN-TIME + + final OType type = OType.getTypeByClass(iKey.getClass()); + if (type == null) + return; + + OIndexManagerProxy indexManager = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getIndexManager(); + getInternal().setType(type); + indexManager.save(); + } + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + return delegate.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + return delegate.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + return delegate.iterateEntriesMinor(toKey, toInclusive, ascOrder); + } + + public long getSize() { + return delegate.getSize(); + } + + @Override + public long count(final Object iKey) { + return delegate.count(iKey); + } + + @Override + public void flush() { + delegate.flush(); + } + + public OIndex delete() { + return delegate.delete(); + } + + public String getName() { + return delegate.getName(); + } + + public String getType() { + return delegate.getType(); + } + + @Override + public String getAlgorithm() { + return delegate.getAlgorithm(); + } + + public boolean isAutomatic() { + return delegate.isAutomatic(); + } + + @Override + public boolean isUnique() { + return delegate.isUnique(); + } + + public ODocument getConfiguration() { + return delegate.getConfiguration(); + } + + @Override + public ODocument getMetadata() { + return delegate.getMetadata(); + } + + public long rebuild() { + return delegate.rebuild(); + } + + public long rebuild(final OProgressListener iProgressListener) { + return delegate.rebuild(iProgressListener); + } + + public OType[] getKeyTypes() { + return delegate.getKeyTypes(); + } + + public OIndexDefinition getDefinition() { + return delegate.getDefinition(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OIndexAbstractDelegate that = (OIndexAbstractDelegate) o; + + if (!delegate.equals(that.delegate)) + return false; + + return true; + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + return delegate.iterateEntries(keys, ascSortOrder); + } + + public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) { + return delegate.checkEntry(iRecord, iKey); + } + + public Set getClusters() { + return delegate.getClusters(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + public long getKeySize() { + return delegate.getKeySize(); + } + + public String getDatabaseName() { + return delegate.getDatabaseName(); + } + + @Override + public boolean supportsOrderedIterations() { + return delegate.supportsOrderedIterations(); + } + + @Override + public boolean isRebuilding() { + return delegate.isRebuilding(); + } + + @Override + public Object getFirstKey() { + return delegate.getFirstKey(); + } + + @Override + public Object getLastKey() { + return delegate.getLastKey(); + } + + @Override + public int getIndexId() { + return delegate.getIndexId(); + } + + @Override + public OIndexCursor cursor() { + return delegate.cursor(); + } + + @Override + public OIndexCursor descCursor() { + return delegate.descCursor(); + } + + @Override + public OIndexKeyCursor keyCursor() { + return delegate.keyCursor(); + } + + @Override + public int compareTo(OIndex o) { + return delegate.compareTo(o); + } + + @Override + public int getVersion() { + return delegate.getVersion(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCallback.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCallback.java new file mode 100644 index 00000000000..96595add293 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCallback.java @@ -0,0 +1,26 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public interface OIndexCallback { + Object getDocumentValueToIndex(ODocument iDocument); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexChangesWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexChangesWrapper.java new file mode 100644 index 00000000000..15d96b8c4ae --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexChangesWrapper.java @@ -0,0 +1,187 @@ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OIndexIsRebuildingException; + +import java.util.Map; +import java.util.Set; + +/** + * Wrapper which is used to detect whether the cursor is working with index + * which is being rebuilt at the moment. + *

      + * If such situation is detected any call to any method throws {@link OIndexIsRebuildingException} + * + * @see OIndexAbstract#getRebuildVersion() + */ +public class OIndexChangesWrapper implements OIndexCursor { + protected final OIndex source; + protected final OIndexCursor delegate; + protected final long indexRebuildVersion; + + public OIndexChangesWrapper(OIndex source, OIndexCursor delegate, long indexRebuildVersion) { + this.source = source; + this.delegate = delegate; + + this.indexRebuildVersion = indexRebuildVersion; + } + + /** + * Wraps courser only if it is not already wrapped. + * + * @param source Index which is used to create given cursor. + * @param cursor Cursor to wrap. + * @param indexRebuildVersion Rebuild version of index before cursor was created. + * @return Wrapped cursor. + * @see OIndex#getRebuildVersion() + */ + public static OIndexCursor wrap(OIndex source, OIndexCursor cursor, long indexRebuildVersion) { + if (cursor instanceof OIndexChangesWrapper) + return cursor; + + if (cursor instanceof OSizeable) { + return new OIndexChangesSizeable(source, cursor, indexRebuildVersion); + } + + return new OIndexChangesWrapper(source, cursor, indexRebuildVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public void remove() { + delegate.remove(); + } + + /** + * {@inheritDoc} + */ + @Override + public Map.Entry nextEntry() { + if (source.isRebuilding()) + throwRebuildException(); + + final Map.Entry entry = delegate.nextEntry(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return entry; + } + + /** + * {@inheritDoc} + */ + @Override + public Set toValues() { + if (source.isRebuilding()) + throwRebuildException(); + + final Set values = delegate.toValues(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return values; + } + + /** + * {@inheritDoc} + */ + @Override + public Set> toEntries() { + if (source.isRebuilding()) + throwRebuildException(); + + final Set> entries = delegate.toEntries(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return entries; + } + + /** + * {@inheritDoc} + */ + @Override + public Set toKeys() { + if (source.isRebuilding()) + throwRebuildException(); + + final Set keys = delegate.toKeys(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return keys; + } + + /** + * {@inheritDoc} + */ + @Override + public void setPrefetchSize(int prefetchSize) { + delegate.setPrefetchSize(prefetchSize); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() { + if (source.isRebuilding()) + throwRebuildException(); + + final boolean isNext = delegate.hasNext(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return isNext; + } + + /** + * {@inheritDoc} + */ + @Override + public OIdentifiable next() { + if (source.isRebuilding()) + throwRebuildException(); + + final OIdentifiable next = delegate.next(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return next; + } + + protected void throwRebuildException() { + throw new OIndexIsRebuildingException("Index " + source.getName() + " is rebuilding at the moment and can not be used"); + } +} + +/** + * Adds support of {@link OSizeable} interface if index cursor implements it. + */ +class OIndexChangesSizeable extends OIndexChangesWrapper implements OSizeable { + public OIndexChangesSizeable(OIndex source, OIndexCursor delegate, long indexRebuildVersion) { + super(source, delegate, indexRebuildVersion); + } + + @Override + public int size() { + if (source.isRebuilding()) + throwRebuildException(); + + final int size = ((OSizeable) delegate).size(); + + if (source.getRebuildVersion() != indexRebuildVersion) + throwRebuildException(); + + return size; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursor.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursor.java new file mode 100644 index 00000000000..ce9b1e173a1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursor.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Presentation of OrientDB index cursor for point and range queries. Cursor may iterate by several elements even if you do point + * query (query by single key). It is possible if you use not unique index. + * + * Contract of cursor is simple it iterates in some subset of index data till it reaches it's borders in such case + * {@link #nextEntry()} returns null. + * + * Cursor is created as result of index query method such as + * {@link com.orientechnologies.orient.core.index.OIndex#iterateEntriesBetween(Object, boolean, Object, boolean, boolean)} cursor + * instance cannot be used at several threads simultaneously. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/4/14 + */ +public interface OIndexCursor extends Iterator { + /** + * Returns nextEntry element in subset of index data which should be iterated by given cursor. + * + * @return nextEntry element in subset of index data which should be iterated by given cursor or null if all data are + * iterated. + */ + Map.Entry nextEntry(); + + /** + * Accumulates and returns all values of index inside of data subset of cursor. + * + * @return all values of index inside of data subset of cursor. + */ + Set toValues(); + + /** + * Accumulates and returns all entries of index inside of data subset of cursor. + * + * @return all entries of index inside of data subset of cursor. + */ + Set> toEntries(); + + /** + * Accumulates and returns all keys of index inside of data subset of cursor. + * + * @return all keys of index inside of data subset of cursor. + */ + Set toKeys(); + + /** + * Set number of records to fetch for the next call to next() or nextEntry(). + * + * @param prefetchSize + * Number of records to prefetch. -1 = no prefetch + */ + void setPrefetchSize(int prefetchSize); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorCollectionValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorCollectionValue.java new file mode 100644 index 00000000000..1ba4e84a84f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorCollectionValue.java @@ -0,0 +1,97 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * Implementation of index cursor in case of collection of values which belongs to single key should be returned. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/4/14 + */ +public class OIndexCursorCollectionValue extends OIndexAbstractCursor implements OSizeable { + private final Object key; + private Collection collection; + private Iterator iterator; + + public OIndexCursorCollectionValue(final Collection collection, final Object key) { + this.collection = collection; + this.iterator = collection.iterator(); + this.key = key; + } + + @Override + public boolean hasNext() { + if (iterator == null) + return false; + + if (!iterator.hasNext()) { + iterator = null; + return false; + } + + return true; + } + + @Override + public OIdentifiable next() { + return iterator.next(); + } + + @Override + public Map.Entry nextEntry() { + if (iterator == null) + return null; + + if (!iterator.hasNext()) { + iterator = null; + return null; + } + + final OIdentifiable value = iterator.next(); + return new Map.Entry() { + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return value; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + + @Override + public int size() { + return collection.size(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorSingleValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorSingleValue.java new file mode 100755 index 00000000000..587e6b922aa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexCursorSingleValue.java @@ -0,0 +1,93 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Implementation of index cursor in case of only single entree should be returned. + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/4/14 + */ +public class OIndexCursorSingleValue extends OIndexAbstractCursor implements OSizeable { + private final Object key; + private OIdentifiable identifiable; + boolean empty = false; + + public OIndexCursorSingleValue(OIdentifiable identifiable, Object key) { + this.identifiable = identifiable; + this.key = key; + if (this.identifiable == null) { + empty = true; + } + } + + @Override + public boolean hasNext() { + return identifiable != null; + } + + @Override + public OIdentifiable next() { + if (identifiable == null) + throw new NoSuchElementException(); + + final OIdentifiable value = identifiable; + identifiable = null; + return value; + } + + @Override + public Map.Entry nextEntry() { + if (identifiable == null) + return null; + + final OIdentifiable value = identifiable; + identifiable = null; + + return new Map.Entry() { + + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return value; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + + @Override + public int size() { + return empty ? 0 : 1; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinition.java new file mode 100755 index 00000000000..0b259f2c21c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinition.java @@ -0,0 +1,136 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.List; + +/** + * Presentation of index that is used information and contained in document + * {@link com.orientechnologies.orient.core.metadata.schema.OClass} . + * + * This object cannot be created directly, use {@link com.orientechnologies.orient.core.metadata.schema.OClass} manipulation method + * instead. + * + * @author Andrey Lomakin, Artem Orobets + */ +public interface OIndexDefinition extends OIndexCallback { + /** + * @return Names of fields which given index is used to calculate key value. Order of fields is important. + */ + List getFields(); + + /** + * @return Names of fields and their index modifiers (like "by value" for fields that hold Map values) which given + * index is used to calculate key value. Order of fields is important. + */ + List getFieldsToIndex(); + + /** + * @return Name of the class which this index belongs to. + */ + String getClassName(); + + /** + * {@inheritDoc} + */ + boolean equals(Object index); + + /** + * {@inheritDoc} + */ + int hashCode(); + + /** + * {@inheritDoc} + */ + String toString(); + + /** + * Calculates key value by passed in parameters. + * + * If it is impossible to calculate key value by given parameters null will be returned. + * + * @param params + * Parameters from which index key will be calculated. + * + * @return Key value or null if calculation is impossible. + */ + Object createValue(List params); + + /** + * Calculates key value by passed in parameters. + * + * If it is impossible to calculate key value by given parameters null will be returned. + * + * + * @param params + * Parameters from which index key will be calculated. + * + * @return Key value or null if calculation is impossible. + */ + Object createValue(Object... params); + + /** + * Returns amount of parameters that are used to calculate key value. It does not mean that all parameters should be supplied. It + * only means that if you provide more parameters they will be ignored and will not participate in index key calculation. + * + * @return Amount of that are used to calculate key value. Call result should be equals to {@code getTypes().length}. + */ + int getParamCount(); + + /** + * Return types of values from which index key consist. In case of index that is built on single document property value single + * array that contains property type will be returned. In case of composite indexes result will contain several key types. + * + * @return Types of values from which index key consist. + */ + OType[] getTypes(); + + /** + * Serializes internal index state to document. + * + * @return Document that contains internal index state. + */ + ODocument toStream(); + + /** + * Deserialize internal index state from document. + * + * @param document + * Serialized index presentation. + */ + void fromStream(ODocument document); + + String toCreateIndexDDL(String indexName, String indexType, String engine); + + boolean isAutomatic(); + + OCollate getCollate(); + + void setCollate(OCollate collate); + + boolean isNullValuesIgnored(); + + void setNullValuesIgnored(boolean value); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionFactory.java new file mode 100755 index 00000000000..28be50129cb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionFactory.java @@ -0,0 +1,216 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Contains helper methods for {@link OIndexDefinition} creation. + *

      + * IMPORTANT: This class designed for internal usage only. + * + * @author Artem Orobets + */ +public class OIndexDefinitionFactory { + private static final Pattern FILED_NAME_PATTERN = Pattern.compile("\\s+"); + + /** + * Creates an instance of {@link OIndexDefinition} for automatic index. + * + * @param oClass + * class which will be indexed + * @param fieldNames + * list of properties which will be indexed. Format should be ' [by key|value]', use 'by key' or 'by value' to + * describe how to index maps. By default maps indexed by key + * @param types + * types of indexed properties + * @param collates + * @param indexKind + * @param algorithm + * @return index definition instance + */ + public static OIndexDefinition createIndexDefinition(final OClass oClass, final List fieldNames, final List types, + List collates, String indexKind, String algorithm) { + checkTypes(oClass, fieldNames, types); + + if (fieldNames.size() == 1) + return createSingleFieldIndexDefinition(oClass, fieldNames.get(0), types.get(0), collates == null ? null : collates.get(0), + indexKind, algorithm); + else + return createMultipleFieldIndexDefinition(oClass, fieldNames, types, collates, indexKind, algorithm); + } + + /** + * Extract field name from ' [by key|value]' field format. + * + * @param fieldDefinition + * definition of field + * @return extracted property name + */ + public static String extractFieldName(final String fieldDefinition) { + String[] fieldNameParts = FILED_NAME_PATTERN.split(fieldDefinition); + if (fieldNameParts.length == 1) + return fieldDefinition; + if (fieldNameParts.length == 3 && "by".equalsIgnoreCase(fieldNameParts[1])) + return fieldNameParts[0]; + + throw new IllegalArgumentException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldDefinition + '\''); + } + + private static OIndexDefinition createMultipleFieldIndexDefinition(final OClass oClass, final List fieldsToIndex, + final List types, List collates, String indexKind, String algorithm) { + final OIndexFactory factory = OIndexes.getFactory(indexKind, algorithm); + final String className = oClass.getName(); + final OCompositeIndexDefinition compositeIndex = new OCompositeIndexDefinition(className); + + for (int i = 0, fieldsToIndexSize = fieldsToIndex.size(); i < fieldsToIndexSize; i++) { + OCollate collate = null; + if (collates != null) + collate = collates.get(i); + + compositeIndex + .addIndex(createSingleFieldIndexDefinition(oClass, fieldsToIndex.get(i), types.get(i), collate, indexKind, algorithm)); + } + + return compositeIndex; + } + + private static void checkTypes(OClass oClass, List fieldNames, List types) { + if (fieldNames.size() != types.size()) + throw new IllegalArgumentException("Count of field names doesn't match count of field types. It was " + fieldNames.size() + + " fields, but " + types.size() + " types."); + + for (int i = 0, fieldNamesSize = fieldNames.size(); i < fieldNamesSize; i++) { + String fieldName = fieldNames.get(i); + OType type = types.get(i); + + final OProperty property = oClass.getProperty(fieldName); + if (property != null && !type.equals(property.getType())) { + throw new IllegalArgumentException("Property type list not match with real property types"); + } + } + } + + private static OIndexDefinition createSingleFieldIndexDefinition(OClass oClass, final String field, final OType type, + OCollate collate, String indexKind, String algorithm) { + + final String fieldName = OClassImpl.decodeClassName(adjustFieldName(oClass, extractFieldName(field))); + final OIndexDefinition indexDefinition; + + final OProperty propertyToIndex = oClass.getProperty(fieldName); + final OType indexType; + if (type == OType.EMBEDDEDMAP || type == OType.LINKMAP) { + final OPropertyMapIndexDefinition.INDEX_BY indexBy = extractMapIndexSpecifier(field); + + if (indexBy.equals(OPropertyMapIndexDefinition.INDEX_BY.KEY)) + indexType = OType.STRING; + else { + if (type == OType.LINKMAP) + indexType = OType.LINK; + else { + indexType = propertyToIndex.getLinkedType(); + if (indexType == null) + throw new OIndexException("Linked type was not provided." + + " You should provide linked type for embedded collections that are going to be indexed."); + } + + } + + indexDefinition = new OPropertyMapIndexDefinition(oClass.getName(), fieldName, indexType, indexBy); + } else if (type.equals(OType.EMBEDDEDLIST) || type.equals(OType.EMBEDDEDSET) || type.equals(OType.LINKLIST) + || type.equals(OType.LINKSET)) { + if (type.equals(OType.LINKSET)) + indexType = OType.LINK; + else if (type.equals(OType.LINKLIST)) { + indexType = OType.LINK; + } else { + indexType = propertyToIndex.getLinkedType(); + if (indexType == null) + throw new OIndexException("Linked type was not provided." + + " You should provide linked type for embedded collections that are going to be indexed."); + } + + indexDefinition = new OPropertyListIndexDefinition(oClass.getName(), fieldName, indexType); + } else if (type.equals(OType.LINKBAG)) { + indexDefinition = new OPropertyRidBagIndexDefinition(oClass.getName(), fieldName); + } else + indexDefinition = new OPropertyIndexDefinition(oClass.getName(), fieldName, type); + + if (collate == null && propertyToIndex != null) + collate = propertyToIndex.getCollate(); + + if (collate != null) + indexDefinition.setCollate(collate); + + return indexDefinition; + } + + private static OPropertyMapIndexDefinition.INDEX_BY extractMapIndexSpecifier(final String fieldName) { + + String[] fieldNameParts = FILED_NAME_PATTERN.split(fieldName); + if (fieldNameParts.length == 1) + return OPropertyMapIndexDefinition.INDEX_BY.KEY; + + if (fieldNameParts.length == 3) { + Locale locale = getServerLocale(); + + if ("by".equals(fieldNameParts[1].toLowerCase(locale))) + try { + return OPropertyMapIndexDefinition.INDEX_BY.valueOf(fieldNameParts[2].toUpperCase(locale)); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldName + '\'', iae); + } + } + + throw new IllegalArgumentException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldName + '\''); + } + + private static Locale getServerLocale() { + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + OStorage storage = db.getStorage(); + OStorageConfiguration configuration = storage.getConfiguration(); + return configuration.getLocaleInstance(); + } + + private static String adjustFieldName(final OClass clazz, final String fieldName) { + final OProperty property = clazz.getProperty(fieldName); + + if (property != null) + return property.getName(); + else + return fieldName; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionMultiValue.java new file mode 100644 index 00000000000..e79fd6d483c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDefinitionMultiValue.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import java.util.Map; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; + +/** + * Interface that indicates that index definition is based on collection of values but not on single value. + * + * @author Andrey Lomakin + * @since 20.12.11 + */ +public interface OIndexDefinitionMultiValue extends OIndexDefinition { + + /** + * Converts passed in value in the key of single index entry. + * + * @param param + * Value to convert. + * @return Index key. + */ + public Object createSingleValue(final Object... param); + + /** + * Process event that contains operation on collection and extract values that should be added removed from index to reflect + * collection changes in the given index. + * + * @param changeEvent + * Event that describes operation that was performed on collection. + * @param keysToAdd + * Values that should be added to related index. + * @param keysToRemove + * Values that should be removed to related index. + */ + public void processChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDictionary.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDictionary.java new file mode 100755 index 00000000000..f3ff9dbdf1a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexDictionary.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; + +/** + * Dictionary index similar to unique index but does not check for updates, just executes changes. Last put always wins and override + * the previous value. + * + * @author Luca Garulli + */ +public class OIndexDictionary extends OIndexOneValue { + + public OIndexDictionary(String name, String typeId, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, ODocument metadata) { + super(name, typeId, algorithm, version, storage, valueContainerAlgorithm, metadata); + } + + public OIndexOneValue put(Object key, final OIdentifiable value) { + + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) { + keyLockManager.acquireExclusiveLock(key); + } + + try { + acquireSharedLock(); + try { + while (true) { + try { + storage.putIndexValue(indexId, key, value); + return this; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + /** + * Disables check of entries. + */ + @Override + public ODocument checkEntry(final OIdentifiable record, final Object key) { + return null; + } + + public boolean canBeUsedInEqualityOperators() { + return true; + } + + public boolean supportsOrderedIterations() { + return false; + } + + @Override + protected Iterable interpretTxKeyChanges( + OTransactionIndexChangesPerKey changes) { + return changes.interpret(OTransactionIndexChangesPerKey.Interpretation.Dictionary); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngine.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngine.java new file mode 100755 index 00000000000..d8825fcbfc2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngine.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin + * @since 6/29/13 + */ +public interface OIndexEngine { + void init(String indexName, String indexType, OIndexDefinition indexDefinition, boolean isAutomatic, ODocument metadata); + + void flush(); + + void create(OBinarySerializer valueSerializer, boolean isAutomatic, OType[] keyTypes, boolean nullPointerSupport, + OBinarySerializer keySerializer, int keySize, Set clustersToIndex, Map engineProperties, + ODocument metadata); + + void delete(); + + void deleteWithoutLoad(String indexName); + + void load(String indexName, OBinarySerializer valueSerializer, boolean isAutomatic, OBinarySerializer keySerializer, + OType[] keyTypes, boolean nullPointerSupport, int keySize, Map engineProperties); + + boolean contains(Object key); + + boolean remove(Object key); + + void clear(); + + void close(); + + Object get(Object key); + + void put(Object key, Object value); + + /** + * Puts the given value under the given key into this index engine. Validates the operation using the provided validator. + * + * @param key the key to put the value under. + * @param value the value to put. + * @param validator the operation validator. + * + * @return {@code true} if the validator allowed the put, {@code false} otherwise. + * + * @see Validator#validate(Object, Object, Object) + */ + boolean validatedPut(Object key, OIdentifiable value, Validator validator); + + Object getFirstKey(); + + Object getLastKey(); + + OIndexCursor iterateEntriesBetween(Object rangeFrom, boolean fromInclusive, Object rangeTo, boolean toInclusive, + boolean ascSortOrder, ValuesTransformer transformer); + + OIndexCursor iterateEntriesMajor(Object fromKey, boolean isInclusive, boolean ascSortOrder, ValuesTransformer transformer); + + OIndexCursor iterateEntriesMinor(final Object toKey, final boolean isInclusive, boolean ascSortOrder, + ValuesTransformer transformer); + + OIndexCursor cursor(ValuesTransformer valuesTransformer); + + OIndexCursor descCursor(ValuesTransformer valuesTransformer); + + OIndexKeyCursor keyCursor(); + + long size(ValuesTransformer transformer); + + boolean hasRangeQuerySupport(); + + int getVersion(); + + String getName(); + + /** + *

      Acquires exclusive lock in the active atomic operation running on the current thread for this index engine. + * + *

      If this index engine supports a more narrow locking, for example key-based sharding, it may use the provided {@code key} to + * infer a more narrow lock scope, but that is not a requirement. + * + * @param key the index key to lock. + * + * @return {@code true} if this index was locked entirely, {@code false} if this index locking is sensitive to the provided {@code + * key} and only some subset of this index was locked. + */ + boolean acquireAtomicExclusiveLock(Object key); + + String getIndexNameByKey(Object key); + + interface ValuesTransformer { + Collection transformFromValue(Object value); + } + + /** + * Put operation validator. + * + * @param the key type. + * @param the value type. + */ + interface Validator { + + /** + * Indicates that a put request should be silently ignored by the store. + * + * @see #validate(Object, Object, Object) + */ + Object IGNORE = new Object(); + + /** + * Validates the put operation for the given key, the old value and the new value. May throw an exception to abort the current + * put operation with an error. + * + * @param key the put operation key. + * @param oldValue the old value or {@code null} if no value is currently stored. + * @param newValue the new value passed to {@link #validatedPut(Object, OIdentifiable, Validator)}. + * + * @return the new value to store, may differ from the passed one, or the special {@link #IGNORE} value to silently ignore the + * put operation request being processed. + */ + Object validate(K key, V oldValue, V newValue); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngineException.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngineException.java new file mode 100755 index 00000000000..4348a28a0cb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexEngineException.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies. + * * + * * Licensed 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 com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.orient.core.exception.OCoreException; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +public class OIndexEngineException extends OCoreException { + + public OIndexEngineException(OIndexEngineException exception) { + super(exception); + } + + public OIndexEngineException(String message, String componentName) { + super(message, componentName); + } + + public OIndexEngineException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexException.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexException.java new file mode 100755 index 00000000000..40c1ca27205 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexException.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.orient.core.exception.OCoreException; + +public class OIndexException extends OCoreException { + + private static final long serialVersionUID = -2655748565531836968L; + + public OIndexException(OIndexException exception) { + super(exception); + } + + public OIndexException(final String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFactory.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFactory.java new file mode 100755 index 00000000000..05373c84e95 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFactory.java @@ -0,0 +1,62 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Map; +import java.util.Set; + +public interface OIndexFactory { + + int getLastVersion(); + + /** + * @return List of supported indexes of this factory + */ + Set getTypes(); + + /** + * @return List of supported algorithms of this factory + */ + Set getAlgorithms(); + + /** + * Creates an index. + * + * @param name + * @param database + * @param indexType + * index type + * @param algorithm + * @param valueContainerAlgorithm + * @return OIndexInternal + * @throws OConfigurationException + * if index creation failed + */ + OIndexInternal createIndex(String name, ODatabaseDocumentInternal database, String indexType, String algorithm, + String valueContainerAlgorithm, ODocument metadata, int version) throws OConfigurationException; + + OIndexEngine createIndexEngine(String algorithm, String name, Boolean durableInNonTxMode, OStorage storage, int version, + Map engineProperties); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFullText.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFullText.java new file mode 100644 index 00000000000..d465504239f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexFullText.java @@ -0,0 +1,385 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.types.OModifiableBoolean; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainer; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * Fast index for full-text searches. + * + * @author Luca Garulli + */ +public class OIndexFullText extends OIndexMultiValues { + + private static final String CONFIG_STOP_WORDS = "stopWords"; + private static final String CONFIG_SEPARATOR_CHARS = "separatorChars"; + private static final String CONFIG_IGNORE_CHARS = "ignoreChars"; + private static final String CONFIG_INDEX_RADIX = "indexRadix"; + private static final String CONFIG_MIN_WORD_LEN = "minWordLength"; + private static final boolean DEF_INDEX_RADIX = true; + private static final String DEF_SEPARATOR_CHARS = " \r\n\t:;,.|+*/\\=!?[]()"; + private static final String DEF_IGNORE_CHARS = "'\""; + private static final String DEF_STOP_WORDS = + "the in a at as and or for his her " + "him this that what which while " + "up with be was were is"; + private static int DEF_MIN_WORD_LENGTH = 3; + private boolean indexRadix; + private String separatorChars; + private String ignoreChars; + private int minWordLength; + + private Set stopWords; + + public OIndexFullText(String name, String typeId, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, ODocument metadata) { + super(name, typeId, algorithm, version, storage, valueContainerAlgorithm, metadata); + acquireExclusiveLock(); + try { + config(); + configWithMetadata(metadata); + } finally { + releaseExclusiveLock(); + } + } + + /** + * Indexes a value and save the index. Splits the value in single words and index each one. Save of the index is responsibility of + * the caller. + */ + @Override + public OIndexFullText put(Object key, final OIdentifiable singleValue) { + if (key == null) + return this; + + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireExclusiveLock(key); + + try { + final Set words = splitIntoWords(key.toString()); + + // FOREACH WORD CREATE THE LINK TO THE CURRENT DOCUMENT + for (final String word : words) { + acquireSharedLock(); + try { + Set refs; + while (true) { + try { + refs = (Set) storage.getIndexValue(indexId, word); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + final boolean durable; + if (metadata != null && Boolean.TRUE.equals(metadata.field("durableInNonTxMode"))) + durable = true; + else + durable = false; + + final Set refsc = refs; + + // SAVE THE INDEX ENTRY + while (true) { + try { + storage.updateIndexEntry(indexId, word, new Callable() { + @Override + public Object call() throws Exception { + Set result = null; + + if (refsc == null) { + // WORD NOT EXISTS: CREATE THE KEYWORD CONTAINER THE FIRST TIME THE WORD IS FOUND + if (ODefaultIndexFactory.SBTREEBONSAI_VALUE_CONTAINER.equals(valueContainerAlgorithm)) { + result = new OIndexRIDContainer(getName(), durable); + } else { + throw new IllegalStateException("MBRBTreeContainer is not supported any more"); + } + } else { + result = refsc; + } + + // ADD THE CURRENT DOCUMENT AS REF FOR THAT WORD + result.add(singleValue); + + return result; + } + }); + + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + } finally { + releaseSharedLock(); + } + } + return this; + + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + /** + * Splits passed in key on several words and remove records with keys equals to any item of split result and values equals to + * passed in value. + * + * @param key Key to remove. + * @param value Value to remove. + * + * @return true if at least one record is removed. + */ + @Override + public boolean remove(Object key, final OIdentifiable value) { + if (key == null) + return false; + + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireExclusiveLock(key); + try { + final Set words = splitIntoWords(key.toString()); + final OModifiableBoolean removed = new OModifiableBoolean(false); + + for (final String word : words) { + acquireSharedLock(); + try { + Set recs; + while (true) { + try { + recs = (Set) storage.getIndexValue(indexId, word); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + if (recs != null && !recs.isEmpty()) { + while (true) { + try { + storage.updateIndexEntry(indexId, word, new EntityRemover(recs, value, removed)); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + + } + + } + } finally { + releaseSharedLock(); + } + } + + return removed.getValue(); + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + @Override + public OIndexInternal create(OIndexDefinition indexDefinition, String clusterIndexName, Set clustersToIndex, + boolean rebuild, OProgressListener progressListener, OBinarySerializer valueSerializer) { + + if (indexDefinition.getFields().size() > 1) { + throw new OIndexException(type + " indexes cannot be used as composite ones."); + } + + return super.create(indexDefinition, clusterIndexName, clustersToIndex, rebuild, progressListener, valueSerializer); + } + + @Override + public OIndexMultiValues create(String name, OIndexDefinition indexDefinition, String clusterIndexName, + Set clustersToIndex, boolean rebuild, OProgressListener progressListener) { + if (indexDefinition.getFields().size() > 1) { + throw new OIndexException(type + " indexes cannot be used as composite ones."); + } + return super.create(name, indexDefinition, clusterIndexName, clustersToIndex, rebuild, progressListener); + } + + @Override + public ODocument updateConfiguration() { + super.updateConfiguration(); + return ((FullTextIndexConfiguration) configuration) + .updateFullTextIndexConfiguration(separatorChars, ignoreChars, stopWords, minWordLength, indexRadix); + } + + @Override + protected IndexConfiguration indexConfigurationInstance(ODocument document) { + return new FullTextIndexConfiguration(document); + } + + public boolean canBeUsedInEqualityOperators() { + return false; + } + + public boolean supportsOrderedIterations() { + return false; + } + + protected void configWithMetadata(ODocument metadata) { + if (metadata != null) { + if (metadata.containsField(CONFIG_IGNORE_CHARS)) + ignoreChars = (String) metadata.field(CONFIG_IGNORE_CHARS); + + if (metadata.containsField(CONFIG_INDEX_RADIX)) + indexRadix = (Boolean) metadata.field(CONFIG_INDEX_RADIX); + + if (metadata.containsField(CONFIG_SEPARATOR_CHARS)) + separatorChars = (String) metadata.field(CONFIG_SEPARATOR_CHARS); + + if (metadata.containsField(CONFIG_MIN_WORD_LEN)) + minWordLength = (Integer) metadata.field(CONFIG_MIN_WORD_LEN); + + if (metadata.containsField(CONFIG_STOP_WORDS)) + stopWords = new HashSet((Collection) metadata.field(CONFIG_STOP_WORDS)); + } + + } + + protected void config() { + ignoreChars = DEF_IGNORE_CHARS; + indexRadix = DEF_INDEX_RADIX; + separatorChars = DEF_SEPARATOR_CHARS; + minWordLength = DEF_MIN_WORD_LENGTH; + stopWords = new HashSet(OStringSerializerHelper.split(DEF_STOP_WORDS, ' ')); + } + + private Set splitIntoWords(final String iKey) { + final Set result = new HashSet(); + + final List words = new ArrayList(); + OStringSerializerHelper.split(words, iKey, 0, -1, separatorChars); + + final StringBuilder buffer = new StringBuilder(64); + // FOREACH WORD CREATE THE LINK TO THE CURRENT DOCUMENT + + char c; + boolean ignore; + for (String word : words) { + buffer.setLength(0); + + for (int i = 0; i < word.length(); ++i) { + c = word.charAt(i); + ignore = false; + for (int k = 0; k < ignoreChars.length(); ++k) + if (c == ignoreChars.charAt(k)) { + ignore = true; + break; + } + + if (!ignore) + buffer.append(c); + } + + int length = buffer.length(); + + while (length >= minWordLength) { + buffer.setLength(length); + word = buffer.toString(); + + // CHECK IF IT'S A STOP WORD + if (!stopWords.contains(word)) + // ADD THE WORD TO THE RESULT SET + result.add(word); + + if (indexRadix) + length--; + else + break; + } + } + + return result; + } + + private static class EntityRemover implements Callable { + private final Set recs; + private final OIdentifiable value; + private final OModifiableBoolean removed; + + public EntityRemover(Set recs, OIdentifiable value, OModifiableBoolean removed) { + this.recs = recs; + this.value = value; + this.removed = removed; + } + + @Override + public Object call() throws Exception { + if (recs.remove(value)) { + removed.setValue(true); + + if (recs.isEmpty()) + return null; + else + return recs; + + } + + return recs; + } + } + + private final class FullTextIndexConfiguration extends IndexConfiguration { + public FullTextIndexConfiguration(ODocument document) { + super(document); + } + + public synchronized ODocument updateFullTextIndexConfiguration(String separatorChars, String ignoreChars, Set stopWords, + int minWordLength, boolean indexRadix) { + document.field(CONFIG_SEPARATOR_CHARS, separatorChars); + document.field(CONFIG_IGNORE_CHARS, ignoreChars); + document.field(CONFIG_STOP_WORDS, stopWords); + document.field(CONFIG_MIN_WORD_LEN, minWordLength); + document.field(CONFIG_INDEX_RADIX, indexRadix); + + return document; + } + + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexInternal.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexInternal.java new file mode 100755 index 00000000000..7a11ead0dcb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexInternal.java @@ -0,0 +1,195 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; + +import java.util.Collection; +import java.util.concurrent.locks.Lock; + +/** + * Interface to handle index. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public interface OIndexInternal extends OIndex { + + String CONFIG_KEYTYPE = "keyType"; + String CONFIG_AUTOMATIC = "automatic"; + String CONFIG_TYPE = "type"; + String ALGORITHM = "algorithm"; + String VALUE_CONTAINER_ALGORITHM = "valueContainerAlgorithm"; + String CONFIG_NAME = "name"; + String INDEX_DEFINITION = "indexDefinition"; + String INDEX_DEFINITION_CLASS = "indexDefinitionClass"; + String INDEX_VERSION = "indexVersion"; + String METADATA = "metadata"; + + Object getCollatingValue(final Object key); + + /** + * Loads the index giving the configuration. + * + * @param iConfig ODocument instance containing the configuration + */ + boolean loadFromConfiguration(ODocument iConfig); + + /** + * Saves the index configuration to disk. + * + * @return The configuration as ODocument instance + * + * @see #getConfiguration() + */ + ODocument updateConfiguration(); + + /** + * Add given cluster to the list of clusters that should be automatically indexed. + * + * @param iClusterName Cluster to add. + * + * @return Current index instance. + */ + OIndex addCluster(final String iClusterName); + + /** + * Remove given cluster from the list of clusters that should be automatically indexed. + * + * @param iClusterName Cluster to remove. + * + * @return Current index instance. + */ + OIndex removeCluster(final String iClusterName); + + /** + * Indicates whether given index can be used to calculate result of + * {@link com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquality} operators. + * + * @return {@code true} if given index can be used to calculate result of + * {@link com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquality} operators. + */ + boolean canBeUsedInEqualityOperators(); + + boolean hasRangeQuerySupport(); + + /** + * Applies exclusive lock on keys which prevents read/modification of this keys in following methods: + * + *
        + *
      1. {@link #put(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      2. + *
      3. {@link #checkEntry(com.orientechnologies.orient.core.db.record.OIdentifiable, Object)}
      4. + *
      5. {@link #remove(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      6. + *
      7. {@link #remove(Object)}
      8. + *
      + * + *

      + * If you want to lock several keys in single thread, you should pass all those keys in single method call. Several calls of this + * method in single thread are not allowed because it may lead to deadlocks. + *

      + * + * This is internal method and cannot be used by end users. + * + * @param key Keys to lock. + */ + void lockKeysForUpdate(Object... key); + + /** + * Applies exclusive lock on keys which prevents read/modification of this keys in following methods: + * + *
        + *
      1. {@link #put(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      2. + *
      3. {@link #checkEntry(com.orientechnologies.orient.core.db.record.OIdentifiable, Object)}
      4. + *
      5. {@link #remove(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      6. + *
      7. {@link #remove(Object)}
      8. + *
      + * + *

      + * If you want to lock several keys in single thread, you should pass all those keys in single method call. Several calls of this + * method in single thread are not allowed because it may lead to deadlocks. + *

      + * + * This is internal method and cannot be used by end users. + * + * @param keys Keys to lock. + * + * @return the array of locks which should be unlocked when done. + */ + Lock[] lockKeysForUpdate(Collection keys); + + /** + * Release exclusive lock on keys which prevents read/modification of this keys in following methods: + * + *
        + *
      1. {@link #put(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      2. + *
      3. {@link #checkEntry(com.orientechnologies.orient.core.db.record.OIdentifiable, Object)}
      4. + *
      5. {@link #remove(Object, com.orientechnologies.orient.core.db.record.OIdentifiable)}
      6. + *
      7. {@link #remove(Object)}
      8. + *
      + * + * This is internal method and cannot be used by end users. + * + * @param key Keys to unlock. + */ + void releaseKeysForUpdate(Object... key); + + OIndexMetadata loadMetadata(ODocument iConfig); + + void setRebuildingFlag(); + + void close(); + + void preCommit(); + + void addTxOperation(final OTransactionIndexChanges changes); + + void commit(); + + void postCommit(); + + void setType(OType type); + + /** + *

      + * Returns the index name for a key. The name is always the current index name, but in cases where the index supports key-based + * sharding. + * + * @param key the index key. + * + * @return The index name involved + */ + String getIndexNameByKey(Object key); + + /** + *

      + * Acquires exclusive lock in the active atomic operation running on the current thread for this index. + * + *

      + * If this index supports a more narrow locking, for example key-based sharding, it may use the provided {@code key} to infer a + * more narrow lock scope, but that is not a requirement. + * + * @param key the index key to lock. + * + * @return {@code true} if this index was locked entirely, {@code false} if this index locking is sensitive to the provided {@code + * key} and only some subset of this index was locked. + */ + boolean acquireAtomicExclusiveLock(Object key); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexKeyCursor.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexKeyCursor.java new file mode 100644 index 00000000000..66516af91c2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexKeyCursor.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/22/14 + */ +public interface OIndexKeyCursor { + Object next(int prefetchSize); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManager.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManager.java new file mode 100755 index 00000000000..d9b45bc3678 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManager.java @@ -0,0 +1,326 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.util.OApi; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.Collection; +import java.util.Set; + +/** + * Manager of indexes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OIndexManager { + + /** + * Load index manager data from database. + * + * IMPORTANT! Only for internal usage. + * + * @return this + */ + OIndexManager load(); + + /** + * Creates a document where index manager configuration is saved and creates a "dictionary" index. + * + * IMPORTANT! Only for internal usage. + */ + void create(); + + /** + * Drops all indexes and creates them from scratch. + */ + void recreateIndexes(); + + /** + * Returns all indexes registered in database. + * + * @return list of registered indexes. + */ + Collection> getIndexes(); + + /** + * Index by specified name. + * + * @param iName + * name of index + * @return index if one registered in database or null otherwise. + */ + OIndex getIndex(final String iName); + + /** + * Returns the auto-sharding index defined for the class, if any. + * + * @param className + * Class name + */ + OIndex getClassAutoShardingIndex(String className); + + /** + * Checks if index with specified name exists in database. + * + * @param iName + * name of index. + * @return true if index with specified name exists, false otherwise. + */ + boolean existsIndex(final String iName); + + /** + * Creates a new index with default algorithm. + * + * @param iName + * - name of index + * @param iType + * - index type. Specified by plugged index factories. + * @param indexDefinition + * metadata that describes index structure + * @param clusterIdsToIndex + * ids of clusters that index should track for changes. + * @param progressListener + * listener to track task progress. + * @param metadata + * document with additional properties that can be used by index engine. + * @return a newly created index instance + */ + OIndex createIndex(final String iName, final String iType, OIndexDefinition indexDefinition, final int[] clusterIdsToIndex, + final OProgressListener progressListener, ODocument metadata); + + /** + * Creates a new index. + * + * May require quite a long time if big amount of data should be indexed. + * + * @param iName + * name of index + * @param iType + * index type. Specified by plugged index factories. + * @param indexDefinition + * metadata that describes index structure + * @param clusterIdsToIndex + * ids of clusters that index should track for changes. + * @param progressListener + * listener to track task progress. + * @param metadata + * document with additional properties that can be used by index engine. + * @param algorithm + * tip to an index factory what algorithm to use + * @return a newly created index instance + */ + OIndex createIndex(final String iName, final String iType, OIndexDefinition indexDefinition, final int[] clusterIdsToIndex, + final OProgressListener progressListener, ODocument metadata, String algorithm); + + /** + * Drop index with specified name. Do nothing if such index does not exists. + * + * @param iIndexName + * the name of index to drop + * @return this + */ + @OApi(maturity = OApi.MATURITY.STABLE) + OIndexManager dropIndex(final String iIndexName); + + /** + * IMPORTANT! Only for internal usage. + * + * @return name of default cluster. + */ + String getDefaultClusterName(); + + /** + * Sets the new default cluster. + * + * IMPORTANT! Only for internal usage. + * + * @param defaultClusterName + * name of new default cluster + */ + void setDefaultClusterName(String defaultClusterName); + + /** + * Return a dictionary index. Could be helpful to store different kinds of configurations. + * + * @return a dictionary + */ + ODictionary getDictionary(); + + /** + * Flushes all indexes that is registered in this manager. There might be some changes stored in memory, this method ensures that + * all this changed are stored to the disk. + */ + void flush(); + + /** + * Returns a record where configurations are saved. + * + * IMPORTANT! Only for internal usage. + * + * @return a document that used to store index configurations. + */ + ODocument getConfiguration(); + + /** + * Returns list of indexes that contain passed in fields names as their first keys. Order of fields does not matter. + *

      + * All indexes sorted by their count of parameters in ascending order. If there are indexes for the given set of fields in super + * class they will be taken into account. + * + * @param className + * name of class which is indexed. + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + */ + Set> getClassInvolvedIndexes(String className, Collection fields); + + /** + * Returns list of indexes that contain passed in fields names as their first keys. Order of fields does not matter. + *

      + * All indexes sorted by their count of parameters in ascending order. If there are indexes for the given set of fields in super + * class they will be taken into account. + * + * @param className + * name of class which is indexed. + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + */ + Set> getClassInvolvedIndexes(String className, String... fields); + + /** + * Indicates whether given fields are contained as first key fields in class indexes. Order of fields does not matter. If there + * are indexes for the given set of fields in super class they will be taken into account. + * + * @param className + * name of class which contain {@code fields}. + * @param fields + * Field names. + * @return true if given fields are contained as first key fields in class indexes. + */ + boolean areIndexed(String className, Collection fields); + + /** + * @param className + * name of class which contain {@code fields}. + * @param fields + * Field names. + * @return true if given fields are contained as first key fields in class indexes. + * @see #areIndexed(String, java.util.Collection) + */ + boolean areIndexed(String className, String... fields); + + /** + * Gets indexes for a specified class (excluding indexes for sub-classes). + * + * @param className + * name of class which is indexed. + * @return a set of indexes related to specified class + */ + Set> getClassIndexes(String className); + + /** + * Gets indexes for a specified class (excluding indexes for sub-classes). + * + * @param className + * name of class which is indexed. + * @param indexes + * Collection of indexes where to add all the indexes + */ + void getClassIndexes(String className, Collection> indexes); + + /** + * Returns the unique index for a class, if any. + */ + OIndexUnique getClassUniqueIndex(String className); + + /** + * Searches for index for a specified class with specified name. + * + * @param className + * name of class which is indexed. + * @param indexName + * name of index. + * @return an index instance or null if such does not exist. + */ + OIndex getClassIndex(String className, String indexName); + + /** + * Blocks current thread till indexes will be restored. + */ + void waitTillIndexRestore(); + + /** + * Checks if indexes should be automatically recreated. + * + * IMPORTANT! Only for internal usage. + * + * @return true if crash is happened and database configured to automatically recreate indexes after crash. + */ + boolean autoRecreateIndexesAfterCrash(); + + /** + * Adds a cluster to tracked cluster list of specified index. + * + * IMPORTANT! Only for internal usage. + * + * @param clusterName + * cluster to add. + * @param indexName + * name of index. + */ + void addClusterToIndex(String clusterName, String indexName); + + /** + * Removes a cluster from tracked cluster list of specified index. + * + * IMPORTANT! Only for internal usage. + * + * @param clusterName + * cluster to remove. + * @param indexName + * name of index. + */ + void removeClusterFromIndex(String clusterName, String indexName); + + /** + * Saves index manager data. + * + * IMPORTANT! Only for internal usage. + */ + RET save(); + + /** + * Removes index from class-property map. + * + * IMPORTANT! Only for internal usage. + * + * @param idx + * index to remove. + */ + void removeClassPropertyIndex(OIndex idx); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerAbstract.java new file mode 100755 index 00000000000..9a7397f777a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerAbstract.java @@ -0,0 +1,531 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.concur.resource.OCloseable; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OMultiKey; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.OMetadata; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sharding.auto.OAutoShardingIndexFactory; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.type.ODocumentWrapper; +import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Abstract class to manage indexes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings({ "unchecked", "serial" }) +public abstract class OIndexManagerAbstract extends ODocumentWrapperNoClass implements OIndexManager, OCloseable { + public static final String CONFIG_INDEXES = "indexes"; + public static final String DICTIONARY_NAME = "dictionary"; + + // values of this Map should be IMMUTABLE !! for thread safety reasons. + protected final Map>>> classPropertyIndex = new ConcurrentHashMap>>>(); + protected Map> indexes = new ConcurrentHashMap>(); + protected String defaultClusterName = OMetadataDefault.CLUSTER_INDEX_NAME; + protected String manualClusterName = OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME; + + protected ReadWriteLock lock = new ReentrantReadWriteLock(); + + public OIndexManagerAbstract(final ODatabaseDocument iDatabase) { + super(new ODocument().setTrackingChanges(false)); + } + + @Override + public OIndexManagerAbstract load() { + if (!autoRecreateIndexesAfterCrash()) { + acquireExclusiveLock(); + try { + if (getDatabase().getStorage().getConfiguration().indexMgrRecordId == null) + // @COMPATIBILITY: CREATE THE INDEX MGR + create(); + + // RELOAD IT + ((ORecordId) document.getIdentity()).fromString(getDatabase().getStorage().getConfiguration().indexMgrRecordId); + super.reload("*:-1 index:0"); + } finally { + releaseExclusiveLock(); + } + } + return this; + } + + @Override + public RET reload() { + acquireExclusiveLock(); + try { + return (RET) super.reload(); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public RET save() { + + OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public Object call() throws Exception { + acquireExclusiveLock(); + + try { + boolean saved = false; + for (int retry = 0; retry < 10; retry++) + try { + + toStream(); + document.save(); + saved = true; + break; + + } catch (OConcurrentModificationException e) { + OLogManager.instance().debug(this, "concurrent modification while saving index manager configuration", e); + reload(null, true); + } + + if (!saved) + OLogManager.instance().error(this, "failed to save the index manager configuration after 10 retries"); + + return null; + + } finally { + releaseExclusiveLock(); + } + } + }); + + return (RET) this; + } + + public void create() { + acquireExclusiveLock(); + try { + try { + save(OMetadataDefault.CLUSTER_INTERNAL_NAME); + } catch (Exception e) { + // RESET RID TO ALLOCATE A NEW ONE + if (ORecordId.isPersistent(document.getIdentity().getClusterPosition())) { + document.getIdentity().reset(); + save(OMetadataDefault.CLUSTER_INTERNAL_NAME); + } + } + getDatabase().getStorage().getConfiguration().indexMgrRecordId = document.getIdentity().toString(); + getDatabase().getStorage().getConfiguration().update(); + + OIndexFactory factory = OIndexes.getFactory(OClass.INDEX_TYPE.DICTIONARY.toString(), null); + createIndex(DICTIONARY_NAME, OClass.INDEX_TYPE.DICTIONARY.toString(), + new OSimpleKeyIndexDefinition(factory.getLastVersion(), OType.STRING), null, null, null); + } finally { + releaseExclusiveLock(); + } + } + + public void flush() { + for (final OIndex idx : indexes.values()) { + OIndexInternal indexInternal = idx.getInternal(); + if (indexInternal != null) + indexInternal.flush(); + } + } + + public Collection> getIndexes() { + final Collection> rawResult = indexes.values(); + final List> result = new ArrayList>(rawResult.size()); + for (final OIndex index : rawResult) + result.add(preProcessBeforeReturn(index)); + return result; + } + + public OIndex getIndex(final String iName) { + final Locale locale = getServerLocale(); + + final OIndex index = indexes.get(iName.toLowerCase(locale)); + if (index == null) + return null; + return preProcessBeforeReturn(index); + } + + @Override + public void addClusterToIndex(final String clusterName, final String indexName) { + final Locale locale = getServerLocale(); + + final OIndex index = indexes.get(indexName.toLowerCase(locale)); + if (index == null) + throw new OIndexException("Index with name " + indexName + " does not exist."); + + if (index.getInternal() == null) + throw new OIndexException("Index with name " + indexName + " has no internal presentation."); + if (!index.getInternal().getClusters().contains(clusterName)) { + index.getInternal().addCluster(clusterName); + save(); + } + } + + @Override + public void removeClusterFromIndex(final String clusterName, final String indexName) { + final Locale locale = getServerLocale(); + + final OIndex index = indexes.get(indexName.toLowerCase(locale)); + if (index == null) + throw new OIndexException("Index with name " + indexName + " does not exist."); + index.getInternal().removeCluster(clusterName); + save(); + } + + public boolean existsIndex(final String iName) { + final Locale locale = getServerLocale(); + return indexes.containsKey(iName.toLowerCase(locale)); + } + + public String getDefaultClusterName() { + acquireSharedLock(); + try { + return defaultClusterName; + } finally { + releaseSharedLock(); + } + } + + public void setDefaultClusterName(final String defaultClusterName) { + acquireExclusiveLock(); + try { + this.defaultClusterName = defaultClusterName; + } finally { + releaseExclusiveLock(); + } + } + + public ODictionary getDictionary() { + OIndex idx; + acquireSharedLock(); + try { + idx = getIndex(DICTIONARY_NAME); + } finally { + releaseSharedLock(); + } + // we lock exclusively only when ODictionary not found + if (idx == null) { + idx = createDictionaryIfNeeded(); + } + return new ODictionary((OIndex) idx); + } + + public ODocument getConfiguration() { + acquireSharedLock(); + + try { + return getDocument(); + } finally { + releaseSharedLock(); + } + + } + + @Override + public void close() { + indexes.clear(); + classPropertyIndex.clear(); + } + + public OIndexManager setDirty() { + acquireExclusiveLock(); + try { + document.setDirty(); + return this; + } finally { + releaseExclusiveLock(); + } + } + + public Set> getClassInvolvedIndexes(final String className, Collection fields) { + fields = normalizeFieldNames(fields); + + final OMultiKey multiKey = new OMultiKey(fields); + + final Map>> propertyIndex = getIndexOnProperty(className); + + if (propertyIndex == null || !propertyIndex.containsKey(multiKey)) + return Collections.emptySet(); + + final Set> rawResult = propertyIndex.get(multiKey); + final Set> transactionalResult = new HashSet>(rawResult.size()); + for (final OIndex index : rawResult) { + //ignore indexes that ignore null values on partial match + if (fields.size() == index.getDefinition().getFields().size() || !index.getDefinition().isNullValuesIgnored()) { + transactionalResult.add(preProcessBeforeReturn(index)); + } + } + + return transactionalResult; + } + + public Set> getClassInvolvedIndexes(final String className, final String... fields) { + return getClassInvolvedIndexes(className, Arrays.asList(fields)); + } + + public boolean areIndexed(final String className, Collection fields) { + fields = normalizeFieldNames(fields); + + final OMultiKey multiKey = new OMultiKey(fields); + + final Map>> propertyIndex = getIndexOnProperty(className); + + if (propertyIndex == null) + return false; + + return propertyIndex.containsKey(multiKey) && !propertyIndex.get(multiKey).isEmpty(); + } + + public boolean areIndexed(final String className, final String... fields) { + return areIndexed(className, Arrays.asList(fields)); + } + + public Set> getClassIndexes(final String className) { + final HashSet> coll = new HashSet>(4); + getClassIndexes(className, coll); + return coll; + } + + @Override + public void getClassIndexes(final String className, final Collection> indexes) { + final Map>> propertyIndex = getIndexOnProperty(className); + + if (propertyIndex == null) + return; + + for (final Set> propertyIndexes : propertyIndex.values()) + for (final OIndex index : propertyIndexes) + indexes.add(preProcessBeforeReturn(index)); + } + + @Override + public OIndexUnique getClassUniqueIndex(final String className) { + final Map>> propertyIndex = getIndexOnProperty(className); + + if (propertyIndex != null) + for (final Set> propertyIndexes : propertyIndex.values()) + for (final OIndex index : propertyIndexes) + if (index instanceof OIndexUnique) + return (OIndexUnique) index; + + return null; + } + + public OIndex getClassIndex(String className, String indexName) { + final Locale locale = getServerLocale(); + className = className.toLowerCase(locale); + indexName = indexName.toLowerCase(locale); + + final OIndex index = indexes.get(indexName); + if (index != null && index.getDefinition() != null && index.getDefinition().getClassName() != null && className + .equals(index.getDefinition().getClassName().toLowerCase(locale))) + return preProcessBeforeReturn(index); + return null; + } + + @Override + public OIndex getClassAutoShardingIndex(String className) { + final Locale locale = getServerLocale(); + className = className.toLowerCase(locale); + + // LOOK FOR INDEX + for (OIndex index : indexes.values()) { + if (index != null && OAutoShardingIndexFactory.AUTOSHARDING_ALGORITHM.equals(index.getAlgorithm()) + && index.getDefinition() != null && index.getDefinition().getClassName() != null && className + .equals(index.getDefinition().getClassName().toLowerCase(locale))) + return preProcessBeforeReturn(index); + } + return null; + } + + protected void acquireSharedLock() { + lock.readLock().lock(); + } + + protected void releaseSharedLock() { + lock.readLock().unlock(); + + } + + protected void acquireExclusiveLock() { + final ODatabaseDocument databaseRecord = getDatabaseIfDefined(); + if (databaseRecord != null && !databaseRecord.isClosed()) { + final OMetadataInternal metadata = (OMetadataInternal) databaseRecord.getMetadata(); + if (metadata != null) + metadata.makeThreadLocalSchemaSnapshot(); + } + + lock.writeLock().lock(); + } + + protected void releaseExclusiveLock() { + lock.writeLock().unlock(); + + final ODatabaseDocument databaseRecord = getDatabaseIfDefined(); + if (databaseRecord != null && !databaseRecord.isClosed()) { + final OMetadata metadata = databaseRecord.getMetadata(); + if (metadata != null) + ((OMetadataInternal) metadata).clearThreadLocalSchemaSnapshot(); + } + } + + protected void clearMetadata() { + acquireExclusiveLock(); + try { + indexes.clear(); + classPropertyIndex.clear(); + } finally { + releaseExclusiveLock(); + } + } + + protected static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + protected ODatabaseDocumentInternal getDatabaseIfDefined() { + return ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + } + + protected void addIndexInternal(final OIndex index) { + acquireExclusiveLock(); + try { + final Locale locale = getServerLocale(); + indexes.put(index.getName().toLowerCase(locale), index); + + final OIndexDefinition indexDefinition = index.getDefinition(); + if (indexDefinition == null || indexDefinition.getClassName() == null) + return; + + Map>> propertyIndex = getIndexOnProperty(indexDefinition.getClassName()); + + if (propertyIndex == null) { + propertyIndex = new HashMap>>(); + } else { + propertyIndex = new HashMap>>(propertyIndex); + } + + final int paramCount = indexDefinition.getParamCount(); + + for (int i = 1; i <= paramCount; i++) { + final List fields = indexDefinition.getFields().subList(0, i); + final OMultiKey multiKey = new OMultiKey(normalizeFieldNames(fields)); + Set> indexSet = propertyIndex.get(multiKey); + + if (indexSet == null) + indexSet = new HashSet>(); + else + indexSet = new HashSet>(indexSet); + + indexSet.add(index); + propertyIndex.put(multiKey, indexSet); + } + + classPropertyIndex.put(indexDefinition.getClassName().toLowerCase(locale), copyPropertyMap(propertyIndex)); + } finally { + releaseExclusiveLock(); + } + } + + protected static Map>> copyPropertyMap(Map>> original) { + final Map>> result = new HashMap>>(); + + for (Map.Entry>> entry : original.entrySet()) { + Set> indexes = new HashSet>(entry.getValue()); + assert indexes.equals(entry.getValue()); + + result.put(entry.getKey(), Collections.unmodifiableSet(indexes)); + } + + assert result.equals(original); + + return Collections.unmodifiableMap(result); + } + + protected List normalizeFieldNames(final Collection fieldNames) { + final Locale locale = getServerLocale(); + final ArrayList result = new ArrayList(fieldNames.size()); + for (final String fieldName : fieldNames) + result.add(fieldName.toLowerCase(locale)); + return result; + } + + protected abstract OIndex preProcessBeforeReturn(final OIndex index); + + private OIndex createDictionaryIfNeeded() { + acquireExclusiveLock(); + try { + OIndex idx = getIndex(DICTIONARY_NAME); + return idx != null ? idx : createDictionary(); + } finally { + releaseExclusiveLock(); + } + } + + private OIndex createDictionary() { + final OIndexFactory factory = OIndexes.getFactory(OClass.INDEX_TYPE.DICTIONARY.toString(), null); + return createIndex(DICTIONARY_NAME, OClass.INDEX_TYPE.DICTIONARY.toString(), + new OSimpleKeyIndexDefinition(factory.getLastVersion(), OType.STRING), null, null, null); + } + + protected Locale getServerLocale() { + ODatabaseDocumentInternal db = getDatabase(); + OStorage storage = db.getStorage(); + OStorageConfiguration configuration = storage.getConfiguration(); + return configuration.getLocaleInstance(); + } + + + private Map>> getIndexOnProperty(final String className) { + final Locale locale = getServerLocale(); + + acquireSharedLock(); + try { + + return classPropertyIndex.get(className.toLowerCase(locale)); + + } finally { + releaseSharedLock(); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerProxy.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerProxy.java new file mode 100755 index 00000000000..8d3ba83d52e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerProxy.java @@ -0,0 +1,196 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.db.record.OProxedResource; +import com.orientechnologies.orient.core.dictionary.ODictionary; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.Collection; +import java.util.Set; + +public class OIndexManagerProxy extends OProxedResource implements OIndexManager { + + public OIndexManagerProxy(final OIndexManager iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + public OIndexManager load() { + return this; + } + + /** + * Force reloading of indexes. + */ + public OIndexManager reload() { + return delegate.load(); + } + + public void create() { + delegate.create(); + } + + public Collection> getIndexes() { + return delegate.getIndexes(); + } + + public OIndex getIndex(final String iName) { + return delegate.getIndex(iName); + } + + public boolean existsIndex(final String iName) { + return delegate.existsIndex(iName); + } + + public OIndex createIndex(final String iName, final String iType, final OIndexDefinition indexDefinition, + final int[] clusterIdsToIndex, final OProgressListener progressListener, final ODocument metadata) { + + if (isDistributedCommand()) { + final OIndexManagerRemote remoteIndexManager = new OIndexManagerRemote(database); + return remoteIndexManager.createIndex(iName, iType, indexDefinition, clusterIdsToIndex, progressListener, metadata); + } + + return delegate.createIndex(iName, iType, indexDefinition, clusterIdsToIndex, progressListener, metadata); + } + + @Override + public OIndex createIndex(final String iName, final String iType, final OIndexDefinition iIndexDefinition, + final int[] iClusterIdsToIndex, final OProgressListener progressListener, final ODocument metadata, final String algorithm) { + if (isDistributedCommand()) { + final OIndexManagerRemote remoteIndexManager = new OIndexManagerRemote(database); + return remoteIndexManager.createIndex(iName, iType, iIndexDefinition, iClusterIdsToIndex, progressListener, metadata, + algorithm); + } + + return delegate.createIndex(iName, iType, iIndexDefinition, iClusterIdsToIndex, progressListener, metadata, algorithm); + } + + public ODocument getConfiguration() { + return delegate.getConfiguration(); + } + + public OIndexManager dropIndex(final String iIndexName) { + if (isDistributedCommand()) { + final OIndexManagerRemote remoteIndexManager = new OIndexManagerRemote(database); + return remoteIndexManager.dropIndex(iIndexName); + } + + return delegate.dropIndex(iIndexName); + } + + public String getDefaultClusterName() { + return delegate.getDefaultClusterName(); + } + + public void setDefaultClusterName(final String defaultClusterName) { + delegate.setDefaultClusterName(defaultClusterName); + } + + public ODictionary getDictionary() { + return delegate.getDictionary(); + } + + public void flush() { + if (delegate != null) + delegate.flush(); + } + + public Set> getClassInvolvedIndexes(final String className, final Collection fields) { + return delegate.getClassInvolvedIndexes(className, fields); + } + + public Set> getClassInvolvedIndexes(final String className, final String... fields) { + return delegate.getClassInvolvedIndexes(className, fields); + } + + public boolean areIndexed(final String className, final Collection fields) { + return delegate.areIndexed(className, fields); + } + + public boolean areIndexed(final String className, final String... fields) { + return delegate.areIndexed(className, fields); + } + + public Set> getClassIndexes(final String className) { + return delegate.getClassIndexes(className); + } + + @Override + public void getClassIndexes(final String className, final Collection> indexes) { + delegate.getClassIndexes(className, indexes); + } + + public OIndex getClassIndex(final String className, final String indexName) { + return delegate.getClassIndex(className, indexName); + } + + @Override + public OIndexUnique getClassUniqueIndex(final String className) { + return delegate.getClassUniqueIndex(className); + } + + public OIndex getClassAutoShardingIndex(final String className) { + return delegate.getClassAutoShardingIndex(className); + } + + @Override + public void recreateIndexes() { + delegate.recreateIndexes(); + } + + @Override + public void waitTillIndexRestore() { + delegate.waitTillIndexRestore(); + } + + @Override + public boolean autoRecreateIndexesAfterCrash() { + return delegate.autoRecreateIndexesAfterCrash(); + } + + @Override + public void addClusterToIndex(String clusterName, String indexName) { + delegate.addClusterToIndex(clusterName, indexName); + } + + @Override + public void removeClusterFromIndex(String clusterName, String indexName) { + delegate.removeClusterFromIndex(clusterName, indexName); + } + + @Override + public RET save() { + return delegate.save(); + } + + public void removeClassPropertyIndex(final OIndex idx) { + delegate.removeClassPropertyIndex(idx); + } + + private boolean isDistributedCommand() { + return database.getStorage().isDistributed() + && !OScenarioThreadLocal.INSTANCE.isRunModeDistributed(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerRemote.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerRemote.java new file mode 100755 index 00000000000..aa9ed71a5de --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerRemote.java @@ -0,0 +1,172 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLCreateIndex; +import com.orientechnologies.orient.core.sql.OCommandSQL; + +import java.util.Collection; +import java.util.Locale; +import java.util.Set; + +public class OIndexManagerRemote extends OIndexManagerAbstract { + private static final String QUERY_DROP = "drop index %s"; + private static final long serialVersionUID = -6570577338095096235L; + + public OIndexManagerRemote(final ODatabaseDocument iDatabase) { + super(iDatabase); + } + + public OIndex createIndex(final String iName, final String iType, final OIndexDefinition iIndexDefinition, + final int[] iClusterIdsToIndex, final OProgressListener progressListener, ODocument metadata, String engine) { + + String createIndexDDL; + if (iIndexDefinition != null) + createIndexDDL = iIndexDefinition.toCreateIndexDDL(iName, iType, engine); + else + createIndexDDL = new OSimpleKeyIndexDefinition().toCreateIndexDDL(iName, iType, engine); + + if (metadata != null) + createIndexDDL += " " + OCommandExecutorSQLCreateIndex.KEYWORD_METADATA + " " + metadata.toJSON(); + + acquireExclusiveLock(); + try { + if (progressListener != null) + progressListener.onBegin(this, 0, false); + + getDatabase().command(new OCommandSQL(createIndexDDL)).execute(); + + ORecordInternal.setIdentity(document, + new ORecordId(ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().indexMgrRecordId)); + + if (progressListener != null) + progressListener.onCompletition(this, true); + + reload(); + + final Locale locale = getServerLocale(); + return preProcessBeforeReturn(indexes.get(iName.toLowerCase(locale))); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public OIndex createIndex(String iName, String iType, OIndexDefinition indexDefinition, int[] clusterIdsToIndex, + OProgressListener progressListener, ODocument metadata) { + return createIndex(iName, iType, indexDefinition, clusterIdsToIndex, progressListener, metadata, null); + } + + public OIndexManager dropIndex(final String iIndexName) { + acquireExclusiveLock(); + try { + final String text = String.format(QUERY_DROP, iIndexName); + getDatabase().command(new OCommandSQL(text)).execute(); + + // REMOVE THE INDEX LOCALLY + final Locale locale = getServerLocale(); + indexes.remove(iIndexName.toLowerCase(locale)); + reload(); + + return this; + } finally { + releaseExclusiveLock(); + } + } + + @Override + public ODocument toStream() { + throw new UnsupportedOperationException("Remote index cannot be streamed"); + } + + @Override + public void recreateIndexes() { + throw new UnsupportedOperationException("recreateIndexes()"); + } + + @Override + public void waitTillIndexRestore() { + } + + @Override + public boolean autoRecreateIndexesAfterCrash() { + return false; + } + + @Override + public void removeClassPropertyIndex(OIndex idx) { + } + + protected OIndex getRemoteIndexInstance(boolean isMultiValueIndex, String type, String name, String algorithm, + Set clustersToIndex, OIndexDefinition indexDefinition, ORID identity, ODocument configuration) { + if (isMultiValueIndex) + return new OIndexRemoteMultiValue(name, type, algorithm, identity, indexDefinition, configuration, clustersToIndex); + + return new OIndexRemoteOneValue(name, type, algorithm, identity, indexDefinition, configuration, clustersToIndex); + } + + @Override + protected void fromStream() { + acquireExclusiveLock(); + try { + clearMetadata(); + + final Collection idxs = document.field(CONFIG_INDEXES); + if (idxs != null) { + for (ODocument d : idxs) { + d.setLazyLoad(false); + try { + final boolean isMultiValue = ODefaultIndexFactory.isMultiValueIndex((String) d.field(OIndexInternal.CONFIG_TYPE)); + + final OIndexMetadata newIndexMetadata = OIndexAbstract.loadMetadataInternal(d, + (String) d.field(OIndexInternal.CONFIG_TYPE), d. field(OIndexInternal.ALGORITHM), + d. field(OIndexInternal.VALUE_CONTAINER_ALGORITHM)); + + addIndexInternal(getRemoteIndexInstance(isMultiValue, newIndexMetadata.getType(), newIndexMetadata.getName(), + newIndexMetadata.getAlgorithm(), newIndexMetadata.getClustersToIndex(), newIndexMetadata.getIndexDefinition(), + (ORID) d.field(OIndexAbstract.CONFIG_MAP_RID), d)); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on loading of index by configuration: %s", e, d); + } + } + } + } finally { + releaseExclusiveLock(); + } + } + + protected OIndex preProcessBeforeReturn(final OIndex index) { + if (index instanceof OIndexRemoteMultiValue) + return new OIndexTxAwareMultiValue(getDatabase(), (OIndex>) index); + else if (index instanceof OIndexRemoteOneValue) + return new OIndexTxAwareOneValue(getDatabase(), (OIndex) index); + return index; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerShared.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerShared.java new file mode 100755 index 00000000000..ebf2c1b98bb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexManagerShared.java @@ -0,0 +1,679 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OMultiKey; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.db.record.OTrackedSet; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OSchemaShared; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.metadata.security.OSecurityNull; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.*; + +/** + * Manages indexes at database level. A single instance is shared among multiple databases. Contentions are managed by r/w locks. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @author Artem Orobets added composite index managemement + */ +@SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS") +public class OIndexManagerShared extends OIndexManagerAbstract { + private static final long serialVersionUID = 1L; + + protected volatile transient Thread recreateIndexesThread = null; + private volatile boolean rebuildCompleted = false; + + public OIndexManagerShared(final ODatabaseDocument iDatabase) { + super(iDatabase); + } + + public OIndex getIndexInternal(final String name) { + acquireSharedLock(); + try { + final Locale locale = getServerLocale(); + return indexes.get(name.toLowerCase(locale)); + } finally { + releaseSharedLock(); + } + } + + /** + * Create a new index with default algorithm. + * + * @param iName name of index + * @param iType index type. Specified by plugged index factories. + * @param indexDefinition metadata that describes index structure + * @param clusterIdsToIndex ids of clusters that index should track for changes. + * @param progressListener listener to track task progress. + * @param metadata document with additional properties that can be used by index engine. + * + * @return a newly created index instance + */ + public OIndex createIndex(final String iName, final String iType, final OIndexDefinition indexDefinition, + final int[] clusterIdsToIndex, OProgressListener progressListener, ODocument metadata) { + return createIndex(iName, iType, indexDefinition, clusterIdsToIndex, progressListener, metadata, null); + } + + /** + * Create a new index. + *

      + * May require quite a long time if big amount of data should be indexed. + * + * @param iName name of index + * @param type index type. Specified by plugged index factories. + * @param indexDefinition metadata that describes index structure + * @param clusterIdsToIndex ids of clusters that index should track for changes. + * @param progressListener listener to track task progress. + * @param metadata document with additional properties that can be used by index engine. + * @param algorithm tip to an index factory what algorithm to use + * + * @return a newly created index instance + */ + public OIndex createIndex(final String iName, String type, final OIndexDefinition indexDefinition, + final int[] clusterIdsToIndex, OProgressListener progressListener, ODocument metadata, String algorithm) { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot create a new index inside a transaction"); + + final Character c = OSchemaShared.checkFieldNameIfValid(iName); + if (c != null) + throw new IllegalArgumentException("Invalid index name '" + iName + "'. Character '" + c + "' is invalid"); + + ODatabaseInternal database = getDatabase(); + OStorage storage = database.getStorage(); + + final Locale locale = getServerLocale(); + type = type.toUpperCase(locale); + if (algorithm == null) { + algorithm = OIndexes.chooseDefaultIndexAlgorithm(type); + } + + final String valueContainerAlgorithm = chooseContainerAlgorithm(type); + + final OIndexInternal index; + acquireExclusiveLock(); + try { + + if (indexes.containsKey(iName.toLowerCase(locale))) + throw new OIndexException("Index with name " + iName.toLowerCase(locale) + " already exists."); + + // manual indexes are always durable + if (clusterIdsToIndex == null || clusterIdsToIndex.length == 0) { + if (metadata == null) + metadata = new ODocument().setTrackingChanges(false); + + final Object durable = metadata.field("durableInNonTxMode"); + if (!(durable instanceof Boolean)) + metadata.field("durableInNonTxMode", true); + if (metadata.field("trackMode") == null) + metadata.field("trackMode", "FULL"); + } + + index = OIndexes.createIndex(getDatabase(), iName, type, algorithm, valueContainerAlgorithm, metadata, -1); + if (progressListener == null) + // ASSIGN DEFAULT PROGRESS LISTENER + progressListener = new OIndexRebuildOutputListener(index); + + final Set clustersToIndex = findClustersByIds(clusterIdsToIndex, database); + if (indexDefinition != null) { + Object ignoreNullValues = metadata == null ? null : metadata.field("ignoreNullValues"); + if (Boolean.TRUE.equals(ignoreNullValues)) { + indexDefinition.setNullValuesIgnored(true); + } else if (Boolean.FALSE.equals(ignoreNullValues)) { + indexDefinition.setNullValuesIgnored(false); + } else { + indexDefinition.setNullValuesIgnored(OGlobalConfiguration.INDEX_IGNORE_NULL_VALUES_DEFAULT.getValueAsBoolean()); + } + } + + // decide which cluster to use ("index" - for automatic and "manindex" for manual) + final String clusterName = + indexDefinition != null && indexDefinition.getClassName() != null ? defaultClusterName : manualClusterName; + + index.create(iName, indexDefinition, clusterName, clustersToIndex, true, progressListener); + + addIndexInternal(index); + + if (metadata != null) { + final ODocument config = index.getConfiguration(); + config.field("metadata", metadata, OType.EMBEDDED); + } + + setDirty(); + save(); + } finally { + releaseExclusiveLock(); + } + + notifyInvolvedClasses(clusterIdsToIndex); + + if (OGlobalConfiguration.INDEX_FLUSH_AFTER_CREATE.getValueAsBoolean()) + storage.synch(); + + return preProcessBeforeReturn(index); + } + + protected void notifyInvolvedClasses(int[] clusterIdsToIndex) { + if (clusterIdsToIndex == null || clusterIdsToIndex.length == 0) + return; + + final ODatabaseDocumentInternal database = getDatabase(); + + // UPDATE INVOLVED CLASSES + final Set classes = new HashSet(); + for (int clusterId : clusterIdsToIndex) { + final OClass cls = database.getMetadata().getSchema().getClassByClusterId(clusterId); + if (cls != null && cls instanceof OClassImpl && !classes.contains(cls.getName())) { + ((OClassImpl) cls).onPostIndexManagement(); + classes.add(cls.getName()); + } + } + } + + private Set findClustersByIds(int[] clusterIdsToIndex, ODatabase database) { + Set clustersToIndex = new HashSet(); + if (clusterIdsToIndex != null) { + for (int clusterId : clusterIdsToIndex) { + final String clusterNameToIndex = database.getClusterNameById(clusterId); + if (clusterNameToIndex == null) + throw new OIndexException("Cluster with id " + clusterId + " does not exist."); + + clustersToIndex.add(clusterNameToIndex); + } + } + return clustersToIndex; + } + + private String chooseContainerAlgorithm(String type) { + final String valueContainerAlgorithm; + if (OClass.INDEX_TYPE.NOTUNIQUE.toString().equals(type) || OClass.INDEX_TYPE.NOTUNIQUE_HASH_INDEX.toString().equals(type) + || OClass.INDEX_TYPE.FULLTEXT_HASH_INDEX.toString().equals(type) || OClass.INDEX_TYPE.FULLTEXT.toString().equals(type)) { + valueContainerAlgorithm = ODefaultIndexFactory.SBTREEBONSAI_VALUE_CONTAINER; + } else { + valueContainerAlgorithm = ODefaultIndexFactory.NONE_VALUE_CONTAINER; + } + return valueContainerAlgorithm; + } + + public OIndexManager dropIndex(final String iIndexName) { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot drop an index inside a transaction"); + + int[] clusterIdsToIndex = null; + + acquireExclusiveLock(); + try { + final Locale locale = getServerLocale(); + final OIndex idx = indexes.remove(iIndexName.toLowerCase(locale)); + if (idx != null) { + final Set clusters = idx.getClusters(); + if (clusters != null && !clusters.isEmpty()) { + final ODatabaseDocumentInternal db = getDatabase(); + clusterIdsToIndex = new int[clusters.size()]; + int i = 0; + for (String cl : clusters) { + clusterIdsToIndex[i++] = db.getClusterIdByName(cl); + } + } + + removeClassPropertyIndex(idx); + + idx.delete(); + setDirty(); + save(); + + notifyInvolvedClasses(clusterIdsToIndex); + } + + } finally { + releaseExclusiveLock(); + } + + return this; + } + + /** + * Binds POJO to ODocument. + */ + @Override + public ODocument toStream() { + acquireExclusiveLock(); + try { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + + try { + final OTrackedSet idxs = new OTrackedSet(document); + + for (final OIndex i : indexes.values()) { + idxs.add(((OIndexInternal) i).updateConfiguration()); + } + document.field(CONFIG_INDEXES, idxs, OType.EMBEDDEDSET); + + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + document.setDirty(); + + return document; + } finally { + releaseExclusiveLock(); + } + } + + @Override + public void recreateIndexes() { + final ODatabaseDocumentInternal db; + + acquireExclusiveLock(); + try { + if (recreateIndexesThread != null && recreateIndexesThread.isAlive()) + // BUILDING ALREADY IN PROGRESS + return; + + db = getDatabase(); + document = db.load(new ORecordId(db.getStorage().getConfiguration().indexMgrRecordId)); + + final Runnable recreateIndexesTask = new RecreateIndexesTask(db.getURL()); + recreateIndexesThread = new Thread(recreateIndexesTask, "OrientDB rebuild indexes"); + recreateIndexesThread.start(); + } finally { + releaseExclusiveLock(); + } + + if (OGlobalConfiguration.INDEX_SYNCHRONOUS_AUTO_REBUILD.getValueAsBoolean()) { + waitTillIndexRestore(); + db.getMetadata().reload(); + } + } + + @Override + public void waitTillIndexRestore() { + if (recreateIndexesThread != null && recreateIndexesThread.isAlive()) { + if (Thread.currentThread().equals(recreateIndexesThread)) + return; + + OLogManager.instance().info(this, "Wait till indexes restore after crash was finished."); + while (recreateIndexesThread.isAlive()) + try { + recreateIndexesThread.join(); + OLogManager.instance().info(this, "Indexes restore after crash was finished."); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + OLogManager.instance().info(this, "Index rebuild task was interrupted."); + } + } + } + + public boolean autoRecreateIndexesAfterCrash() { + if (rebuildCompleted) + return false; + + final ODatabaseDocumentInternal database = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OStorage storage = database.getStorage().getUnderlying(); + if (storage instanceof OAbstractPaginatedStorage) { + OAbstractPaginatedStorage paginatedStorage = (OAbstractPaginatedStorage) storage; + return paginatedStorage.isIndexRebuildScheduled(); + } + + return false; + } + + @Override + protected void fromStream() { + acquireExclusiveLock(); + try { + final Map> oldIndexes = new HashMap>(indexes); + + clearMetadata(); + final Collection idxs = document.field(CONFIG_INDEXES); + final Locale locale = getServerLocale(); + + if (idxs != null) { + OIndexInternal index; + boolean configUpdated = false; + Iterator indexConfigurationIterator = idxs.iterator(); + while (indexConfigurationIterator.hasNext()) { + final ODocument d = indexConfigurationIterator.next(); + try { + final int indexVersion = + d.field(OIndexInternal.INDEX_VERSION) == null ? 1 : (Integer) d.field(OIndexInternal.INDEX_VERSION); + + final OIndexMetadata newIndexMetadata = OIndexAbstract + .loadMetadataInternal(d, (String) d.field(OIndexInternal.CONFIG_TYPE), (String) d.field(OIndexInternal.ALGORITHM), + d.field(OIndexInternal.VALUE_CONTAINER_ALGORITHM)); + + index = OIndexes + .createIndex(getDatabase(), newIndexMetadata.getName(), newIndexMetadata.getType(), newIndexMetadata.getAlgorithm(), + newIndexMetadata.getValueContainerAlgorithm(), (ODocument) d.field(OIndexInternal.METADATA), indexVersion); + + final String normalizedName = newIndexMetadata.getName().toLowerCase(locale); + + OIndex oldIndex = oldIndexes.remove(normalizedName); + if (oldIndex != null) { + OIndexMetadata oldIndexMetadata = oldIndex.getInternal().loadMetadata(oldIndex.getConfiguration()); + + if (!(oldIndexMetadata.equals(newIndexMetadata) || newIndexMetadata.getIndexDefinition() == null)) { + oldIndex.delete(); + } + + if (index.loadFromConfiguration(d)) { + addIndexInternal(index); + } else { + indexConfigurationIterator.remove(); + configUpdated = true; + } + } else { + if (index.loadFromConfiguration(d)) { + addIndexInternal(index); + } else { + indexConfigurationIterator.remove(); + configUpdated = true; + } + } + } catch (RuntimeException e) { + indexConfigurationIterator.remove(); + configUpdated = true; + OLogManager.instance().error(this, "Error on loading index by configuration: %s", e, d); + } + } + + for (OIndex oldIndex : oldIndexes.values()) + try { + OLogManager.instance().warn(this, "Index '%s' was not found after reload and will be removed", oldIndex.getName()); + + oldIndex.delete(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on deletion of index '%s'", e, oldIndex.getName()); + } + + if (configUpdated) { + document.field(CONFIG_INDEXES, idxs); + save(); + } + + } + } finally { + releaseExclusiveLock(); + } + } + + public void removeClassPropertyIndex(final OIndex idx) { + acquireExclusiveLock(); + try { + final OIndexDefinition indexDefinition = idx.getDefinition(); + if (indexDefinition == null || indexDefinition.getClassName() == null) + return; + + final Locale locale = getServerLocale(); + Map>> map = classPropertyIndex.get(indexDefinition.getClassName().toLowerCase(locale)); + + if (map == null) { + return; + } + + map = new HashMap>>(map); + + final int paramCount = indexDefinition.getParamCount(); + + for (int i = 1; i <= paramCount; i++) { + final List fields = normalizeFieldNames(indexDefinition.getFields().subList(0, i)); + final OMultiKey multiKey = new OMultiKey(fields); + + Set> indexSet = map.get(multiKey); + if (indexSet == null) + continue; + + indexSet = new HashSet>(indexSet); + indexSet.remove(idx); + + if (indexSet.isEmpty()) { + map.remove(multiKey); + } else { + map.put(multiKey, indexSet); + } + } + + if (map.isEmpty()) + classPropertyIndex.remove(indexDefinition.getClassName().toLowerCase(locale)); + else + classPropertyIndex.put(indexDefinition.getClassName().toLowerCase(locale), copyPropertyMap(map)); + + } finally { + releaseExclusiveLock(); + } + } + + private class RecreateIndexesTask implements Runnable { + private ODatabaseDocumentInternal newDb; + private Collection indexesToRebuild; + + private final String url; + private int ok; + private int errors; + + public RecreateIndexesTask(String url) { + this.url = url; + } + + @Override + public void run() { + try { + setUpDatabase(); + + final OStorage storage = newDb.getStorage().getUnderlying(); + + if (storage instanceof OAbstractPaginatedStorage) { + final OAbstractPaginatedStorage abstractPaginatedStorage = (OAbstractPaginatedStorage) storage; + abstractPaginatedStorage.getAtomicOperationsManager().switchOnUnsafeMode(); + } + + try { + recreateIndexes(); + } finally { + if (storage instanceof OAbstractPaginatedStorage) { + final OAbstractPaginatedStorage abstractPaginatedStorage = (OAbstractPaginatedStorage) storage; + abstractPaginatedStorage.getAtomicOperationsManager().switchOffUnsafeMode(); + abstractPaginatedStorage.synch(); + } + } + + } catch (Exception e) { + OLogManager.instance().error(this, "Error when attempt to restore indexes after crash was performed", e); + } + } + + private void recreateIndexes() { + ok = 0; + errors = 0; + for (ODocument idx : indexesToRebuild) { + try { + recreateIndex(idx); + + } catch (RuntimeException e) { + OLogManager.instance().error(this, "Error during addition of index '%s'", e, idx); + errors++; + } + } + + newDb.getMetadata().getIndexManager().save(); + + rebuildCompleted = true; + final OStorage storage = newDb.getStorage().getUnderlying(); + + if (storage instanceof OLocalPaginatedStorage) { + final OLocalPaginatedStorage paginatedStorage = (OLocalPaginatedStorage) storage; + try { + paginatedStorage.cancelIndexRebuild(); + } catch (IOException e) { + OLogManager.instance().error(this, "Storage index rebuild flag can not be canceled after index rebuild", e); + } + } + + OLogManager.instance().info(this, "%d indexes were restored successfully, %d errors", ok, errors); + } + + private void recreateIndex(ODocument idx) { + final OIndexInternal index = createIndex(idx); + final OIndexMetadata indexMetadata = index.loadMetadata(idx); + final OIndexDefinition indexDefinition = indexMetadata.getIndexDefinition(); + + if (indexDefinition != null && indexDefinition.isAutomatic()) { + try { + index.loadFromConfiguration(idx); + index.delete(); + } catch (Exception e) { + OLogManager.instance() + .error(this, "Error on removing index '%s' on rebuilding. Trying removing index files (Cause: %s)", index.getName(), + e); + + // TRY DELETING ALL THE FILES RELATIVE TO THE INDEX + for (Iterator it = OIndexes.getAllFactories(); it.hasNext(); ) { + try { + final OIndexFactory indexFactory = it.next(); + final OIndexEngine engine = indexFactory + .createIndexEngine(null, index.getName(), false, getDatabase().getStorage(), 0, null); + + engine.deleteWithoutLoad(index.getName()); + } catch (Exception e2) { + } + } + } + + createAutomaticIndex(idx, index, indexMetadata, indexDefinition); + } else { + addIndexAsIs(idx, index, indexMetadata); + } + } + + private void createAutomaticIndex(ODocument idx, OIndexInternal index, OIndexMetadata indexMetadata, + OIndexDefinition indexDefinition) { + final String indexName = indexMetadata.getName(); + final Set clusters = indexMetadata.getClustersToIndex(); + final String type = indexMetadata.getType(); + + if (indexName != null && clusters != null && !clusters.isEmpty() && type != null) { + OLogManager.instance().info(this, "Start creation of index '%s'", indexName); + index.create(indexName, indexDefinition, defaultClusterName, clusters, false, new OIndexRebuildOutputListener(index)); + + index.setRebuildingFlag(); + addIndexInternal(index); + + OLogManager.instance().info(this, "Index '%s' was successfully created and rebuild is going to be started", indexName); + + index.rebuild(new OIndexRebuildOutputListener(index)); + index.flush(); + + setDirty(); + + ok++; + + OLogManager.instance().info(this, "Rebuild of '%s index was successfully finished", indexName); + } else { + errors++; + OLogManager.instance().error(this, "Information about index was restored incorrectly, following data were loaded : " + + "index name '%s', index definition '%s', clusters %s, type %s", indexName, indexDefinition, clusters, type); + } + } + + private void addIndexAsIs(ODocument idx, OIndexInternal index, OIndexMetadata indexMetadata) { + OLogManager.instance().info(this, "Index '%s' is not automatic index and will be added as is", indexMetadata.getName()); + + if (index.loadFromConfiguration(idx)) { + addIndexInternal(index); + setDirty(); + + ok++; + OLogManager.instance().info(this, "Index '%s' was added in DB index list", index.getName()); + } else { + index.delete(); + errors++; + } + } + + private OIndexInternal createIndex(ODocument idx) { + final String indexName = idx.field(OIndexInternal.CONFIG_NAME); + final String indexType = idx.field(OIndexInternal.CONFIG_TYPE); + String algorithm = idx.field(OIndexInternal.ALGORITHM); + String valueContainerAlgorithm = idx.field(OIndexInternal.VALUE_CONTAINER_ALGORITHM); + + ODocument metadata = idx.field(OIndexInternal.METADATA); + if (indexType == null) { + OLogManager.instance().error(this, "Index type is null, will process other record"); + throw new OIndexException("Index type is null, will process other record. Index configuration: " + idx.toString()); + } + + return OIndexes.createIndex(newDb, indexName, indexType, algorithm, valueContainerAlgorithm, metadata, -1); + } + + private void setUpDatabase() { + newDb = new ODatabaseDocumentTx(url); + newDb.activateOnCurrentThread(); + newDb.resetInitialization(); + newDb.setProperty(ODatabase.OPTIONS.SECURITY.toString(), OSecurityNull.class); + newDb.open("admin", "nopass"); + + acquireExclusiveLock(); + try { + final Collection knownIndexes = document.field(CONFIG_INDEXES); + if (knownIndexes == null) { + OLogManager.instance().warn(this, "List of indexes is empty"); + indexesToRebuild = Collections.emptyList(); + } else { + indexesToRebuild = new ArrayList(); + for (ODocument index : knownIndexes) + indexesToRebuild.add(index.copy()); // make copies to safely iterate them later + } + } finally { + releaseExclusiveLock(); + } + } + } + + protected OIndex preProcessBeforeReturn(final OIndex index) { + if (index instanceof OIndexMultiValues) + return new OIndexTxAwareMultiValue(getDatabase(), (OIndex>) index); + else if (index instanceof OIndexDictionary) + return new OIndexTxAwareDictionary(getDatabase(), (OIndex) index); + else if (index instanceof OIndexOneValue) + return new OIndexTxAwareOneValue(getDatabase(), (OIndex) index); + + return index; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMetadata.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMetadata.java new file mode 100755 index 00000000000..2b7618e01df --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMetadata.java @@ -0,0 +1,104 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import java.util.Set; + +/** + * Contains the index metadata. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OIndexMetadata { + private final String name; + private final OIndexDefinition indexDefinition; + private final Set clustersToIndex; + private final String type; + private final String algorithm; + private final String valueContainerAlgorithm; + + public OIndexMetadata(String name, OIndexDefinition indexDefinition, Set clustersToIndex, String type, String algorithm, + String valueContainerAlgorithm) { + this.name = name; + this.indexDefinition = indexDefinition; + this.clustersToIndex = clustersToIndex; + this.type = type; + this.algorithm = algorithm; + this.valueContainerAlgorithm = valueContainerAlgorithm; + } + + public String getName() { + return name; + } + + public OIndexDefinition getIndexDefinition() { + return indexDefinition; + } + + public Set getClustersToIndex() { + return clustersToIndex; + } + + public String getType() { + return type; + } + + public String getAlgorithm() { + return algorithm; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OIndexMetadata that = (OIndexMetadata) o; + + if (algorithm != null ? !algorithm.equals(that.algorithm) : that.algorithm != null) + return false; + if (!clustersToIndex.equals(that.clustersToIndex)) + return false; + if (indexDefinition != null ? !indexDefinition.equals(that.indexDefinition) : that.indexDefinition != null) + return false; + if (!name.equals(that.name)) + return false; + if (!type.equals(that.type)) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (indexDefinition != null ? indexDefinition.hashCode() : 0); + result = 31 * result + clustersToIndex.hashCode(); + result = 31 * result + type.hashCode(); + result = 31 * result + (algorithm != null ? algorithm.hashCode() : 0); + return result; + } + + String getValueContainerAlgorithm() { + return valueContainerAlgorithm; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMultiValues.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMultiValues.java new file mode 100644 index 00000000000..3276ecf5211 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexMultiValues.java @@ -0,0 +1,496 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.types.OModifiableBoolean; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainer; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerSBTreeIndexRIDContainer; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.*; +import java.util.concurrent.Callable; + +/** + * Abstract index implementation that supports multi-values for the same key. + * + * @author Luca Garulli + */ +public abstract class OIndexMultiValues extends OIndexAbstract> { + public OIndexMultiValues(String name, final String type, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, final ODocument metadata) { + super(name, type, algorithm, valueContainerAlgorithm, metadata, version, storage); + } + + public Set get(Object key) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireSharedLock(key); + try { + + acquireSharedLock(); + try { + + Set values; + + while (true) { + try { + values = (Set) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + if (values == null) + return Collections.emptySet(); + + return Collections.unmodifiableSet(values); + + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(key); + } + } + + public long count(Object key) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + if (!txIsActive) + keyLockManager.acquireSharedLock(key); + try { + acquireSharedLock(); + try { + + Set values; + + while (true) { + try { + values = (Set) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + if (values == null) + return 0; + + return values.size(); + + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(key); + } + + } + + public OIndexMultiValues put(Object key, final OIdentifiable singleValue) { + if (singleValue != null && !singleValue.getIdentity().isPersistent()) + throw new IllegalArgumentException("Cannot index a non persistent record (" + singleValue.getIdentity() + ")"); + + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) { + keyLockManager.acquireExclusiveLock(key); + } + try { + acquireSharedLock(); + + try { + if (!singleValue.getIdentity().isValid()) + (singleValue.getRecord()).save(); + + final ORID identity = singleValue.getIdentity(); + + final boolean durable; + + if (metadata != null && Boolean.TRUE.equals(metadata.field("durableInNonTxMode"))) + durable = true; + else + durable = false; + + Set values = null; + + while (true) { + try { + values = (Set) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + final Set cvalues = values; + + final Callable creator = new Callable() { + @Override + public Object call() throws Exception { + Set result = cvalues; + + if (result == null) { + if (ODefaultIndexFactory.SBTREEBONSAI_VALUE_CONTAINER.equals(valueContainerAlgorithm)) { + result = new OIndexRIDContainer(getName(), durable); + } else { + throw new IllegalStateException("MVRBTree is not supported any more"); + } + } + + result.add(identity); + + return result; + } + }; + + while (true) { + try { + storage.updateIndexEntry(indexId, key, creator); + return this; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + @Override + public boolean remove(Object key, final OIdentifiable value) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireExclusiveLock(key); + + try { + acquireSharedLock(); + try { + Set values = null; + while (true) { + try { + values = (Set) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + if (values == null) { + return false; + } + + final OModifiableBoolean removed = new OModifiableBoolean(false); + + final Callable creator = new EntityRemover(value, removed, values); + + while (true) + try { + storage.updateIndexEntry(indexId, key, creator); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + + return removed.getValue(); + + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + + } + + public OIndexMultiValues create(final String name, final OIndexDefinition indexDefinition, final String clusterIndexName, + final Set clustersToIndex, boolean rebuild, final OProgressListener progressListener) { + + return (OIndexMultiValues) super + .create(indexDefinition, clusterIndexName, clustersToIndex, rebuild, progressListener, determineValueSerializer()); + } + + protected OBinarySerializer determineValueSerializer() { + return storage.getComponentsFactory().binarySerializerFactory.getObjectSerializer(OStreamSerializerSBTreeIndexRIDContainer.ID); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + fromKey = getCollatingValue(fromKey); + toKey = getCollatingValue(toKey); + + acquireSharedLock(); + try { + while (true) + try { + return storage.iterateIndexEntriesBetween(indexId, fromKey, fromInclusive, toKey, toInclusive, ascOrder, + MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + fromKey = getCollatingValue(fromKey); + + acquireSharedLock(); + try { + while (true) { + try { + return storage.iterateIndexEntriesMajor(indexId, fromKey, fromInclusive, ascOrder, MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + toKey = getCollatingValue(toKey); + + acquireSharedLock(); + try { + while (true) { + try { + return storage.iterateIndexEntriesMinor(indexId, toKey, toInclusive, ascOrder, MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + final List sortedKeys = new ArrayList(keys); + final Comparator comparator; + if (ascSortOrder) + comparator = ODefaultComparator.INSTANCE; + else + comparator = Collections.reverseOrder(ODefaultComparator.INSTANCE); + + Collections.sort(sortedKeys, comparator); + + return new OIndexAbstractCursor() { + private Iterator keysIterator = sortedKeys.iterator(); + + private Iterator currentIterator = OEmptyIterator.IDENTIFIABLE_INSTANCE; + private Object currentKey; + + @Override + public Map.Entry nextEntry() { + if (currentIterator == null) + return null; + + Object key = null; + if (!currentIterator.hasNext()) { + Collection result = null; + while (keysIterator.hasNext() && (result == null || result.isEmpty())) { + key = keysIterator.next(); + key = getCollatingValue(key); + + acquireSharedLock(); + try { + while (true) + try { + result = (Collection) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + + } finally { + releaseSharedLock(); + } + } + + if (result == null) { + currentIterator = null; + return null; + } + + currentKey = key; + currentIterator = result.iterator(); + } + + final OIdentifiable resultValue = currentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return currentKey; + } + + @Override + public OIdentifiable getValue() { + return resultValue; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + }; + } + + public long getSize() { + acquireSharedLock(); + try { + while (true) + try { + return storage.getIndexSize(indexId, MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + + } + + public long getKeySize() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexSize(indexId, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor cursor() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexCursor(indexId, MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor descCursor() { + acquireSharedLock(); + try { + while (true) + try { + return storage.getIndexDescCursor(indexId, MultiValuesTransformer.INSTANCE); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + private static final class MultiValuesTransformer implements OIndexEngine.ValuesTransformer { + private static final MultiValuesTransformer INSTANCE = new MultiValuesTransformer(); + + @Override + public Collection transformFromValue(Object value) { + return (Collection) value; + } + } + + private static class EntityRemover implements Callable { + private final OIdentifiable value; + private final OModifiableBoolean removed; + private final Set values; + + public EntityRemover(OIdentifiable value, OModifiableBoolean removed, Set values) { + this.value = value; + this.removed = removed; + this.values = values; + } + + @Override + public Object call() throws Exception { + if (value == null) { + removed.setValue(true); + + return null; + } else if (values.remove(value)) { + removed.setValue(true); + + if (values.isEmpty()) + return null; + else + return values; + } + + return values; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexNotUnique.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexNotUnique.java new file mode 100755 index 00000000000..542b6ffd931 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexNotUnique.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; + +/** + * Index implementation that allows multiple values for the same key. + * + * @author Luca Garulli + */ +public class OIndexNotUnique extends OIndexMultiValues { + + public OIndexNotUnique(String name, String typeId, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, ODocument metadata) { + super(name, typeId, algorithm, version, storage, valueContainerAlgorithm, metadata); + } + + public boolean canBeUsedInEqualityOperators() { + return true; + } + + @Override + public boolean supportsOrderedIterations() { + while (true) + try { + return storage.hasIndexRangeQuerySupport(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + @Override + protected Iterable interpretTxKeyChanges( + OTransactionIndexChangesPerKey changes) { + return changes.interpret(OTransactionIndexChangesPerKey.Interpretation.NonUnique); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexOneValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexOneValue.java new file mode 100644 index 00000000000..be9fb0bd70b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexOneValue.java @@ -0,0 +1,319 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerRID; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.*; + +/** + * Abstract Index implementation that allows only one value for a key. + * + * @author Luca Garulli + */ +public abstract class OIndexOneValue extends OIndexAbstract { + public OIndexOneValue(String name, final String type, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, ODocument metadata) { + super(name, type, algorithm, valueContainerAlgorithm, metadata, version, storage); + } + + public OIdentifiable get(Object iKey) { + iKey = getCollatingValue(iKey); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + if (!txIsActive) + keyLockManager.acquireSharedLock(iKey); + try { + acquireSharedLock(); + try { + while (true) + try { + return (OIdentifiable) storage.getIndexValue(indexId, iKey); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(iKey); + } + + } + + public long count(Object iKey) { + iKey = getCollatingValue(iKey); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + if (!txIsActive) + keyLockManager.acquireSharedLock(iKey); + + try { + acquireSharedLock(); + try { + while (true) + try { + return storage.indexContainsKey(indexId, iKey) ? 1 : 0; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(iKey); + } + } + + @Override + public ODocument checkEntry(final OIdentifiable record, Object key) { + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) + keyLockManager.acquireSharedLock(key); + try { + // CHECK IF ALREADY EXIST + final OIdentifiable indexedRID = get(key); + if (indexedRID != null && !indexedRID.getIdentity().equals(record.getIdentity())) { + final Boolean mergeSameKey = metadata != null && (Boolean) metadata.field(OIndex.MERGE_KEYS); + if (mergeSameKey != null && mergeSameKey) + return (ODocument) indexedRID.getRecord(); + else + throw new ORecordDuplicatedException(String + .format("Cannot index record %s: found duplicated key '%s' in index '%s' previously assigned to the record %s", + record, key, getName(), indexedRID), getName(), indexedRID.getIdentity()); + } + return null; + } finally { + if (!txIsActive) + keyLockManager.releaseSharedLock(key); + } + } + + public OIndexOneValue create(final String name, final OIndexDefinition indexDefinition, final String clusterIndexName, + final Set clustersToIndex, boolean rebuild, final OProgressListener progressListener) { + return (OIndexOneValue) super + .create(indexDefinition, clusterIndexName, clustersToIndex, rebuild, progressListener, determineValueSerializer()); + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + final List sortedKeys = new ArrayList(keys); + final Comparator comparator; + + if (ascSortOrder) + comparator = ODefaultComparator.INSTANCE; + else + comparator = Collections.reverseOrder(ODefaultComparator.INSTANCE); + + Collections.sort(sortedKeys, comparator); + + return new OIndexAbstractCursor() { + private Iterator keysIterator = sortedKeys.iterator(); + + @Override + public Map.Entry nextEntry() { + OIdentifiable result = null; + Object key = null; + while (keysIterator.hasNext() && result == null) { + key = keysIterator.next(); + key = getCollatingValue(key); + + acquireSharedLock(); + try { + while (true) + try { + result = (OIdentifiable) storage.getIndexValue(indexId, key); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + if (result == null) + return null; + + final Object resultKey = key; + final OIdentifiable resultValue = result; + + return new Map.Entry() { + @Override + public Object getKey() { + return resultKey; + } + + @Override + public OIdentifiable getValue() { + return resultValue; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + }; + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + fromKey = getCollatingValue(fromKey); + toKey = getCollatingValue(toKey); + + acquireSharedLock(); + try { + while (true) + try { + return storage.iterateIndexEntriesBetween(indexId, fromKey, fromInclusive, toKey, toInclusive, ascOrder, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + fromKey = getCollatingValue(fromKey); + acquireSharedLock(); + try { + while (true) + try { + return storage.iterateIndexEntriesMajor(indexId, fromKey, fromInclusive, ascOrder, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + toKey = getCollatingValue(toKey); + acquireSharedLock(); + try { + while (true) { + try { + return storage.iterateIndexEntriesMinor(indexId, toKey, toInclusive, ascOrder, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + } finally { + releaseSharedLock(); + } + } + + public long getSize() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexSize(indexId, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + public long getKeySize() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexSize(indexId, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor cursor() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexCursor(indexId, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public OIndexCursor descCursor() { + acquireSharedLock(); + try { + while (true) { + try { + return storage.getIndexDescCursor(indexId, null); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + } finally { + releaseSharedLock(); + } + } + + @Override + public boolean isUnique() { + return true; + } + + @Override + protected OBinarySerializer determineValueSerializer() { + return OStreamSerializerRID.INSTANCE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRebuildOutputListener.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRebuildOutputListener.java new file mode 100755 index 00000000000..a9e0134fd6b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRebuildOutputListener.java @@ -0,0 +1,86 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; + +/** + * Progress listener for index rebuild. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OIndexRebuildOutputListener implements OProgressListener { + long startTime; + long lastDump; + long lastCounter = 0; + boolean rebuild = false; + + private final OIndex idx; + + public OIndexRebuildOutputListener(OIndex idx) { + this.idx = idx; + } + + @Override + public void onBegin(final Object iTask, final long iTotal, final Object iRebuild) { + startTime = System.currentTimeMillis(); + lastDump = startTime; + + rebuild = (Boolean) iRebuild; + if (iTotal > 0) + if (rebuild) + OLogManager.instance().info(this, "- Rebuilding index %s.%s (estimated %,d items)...", idx.getDatabaseName(), + idx.getName(), iTotal); + else + OLogManager.instance().debug(this, "- Building index %s.%s (estimated %,d items)...", idx.getDatabaseName(), idx.getName(), + iTotal); + } + + @Override + public boolean onProgress(final Object iTask, final long iCounter, final float iPercent) { + final long now = System.currentTimeMillis(); + if (now - lastDump > 10000) { + // DUMP EVERY 5 SECONDS FOR LARGE INDEXES + if (rebuild) + OLogManager.instance().info(this, "--> %3.2f%% progress, %,d indexed so far (%,d items/sec)", iPercent, iCounter, + ((iCounter - lastCounter) / 10)); + else + OLogManager.instance().debug(this, "--> %3.2f%% progress, %,d indexed so far (%,d items/sec)", iPercent, iCounter, + ((iCounter - lastCounter) / 10)); + lastDump = now; + lastCounter = iCounter; + } + return true; + } + + @Override + public void onCompletition(final Object iTask, final boolean iSucceed) { + final long idxSize = idx.getSize(); + + if (idxSize > 0) + if (rebuild) + OLogManager.instance().info(this, "--> OK, indexed %,d items in %,d ms", idxSize, (System.currentTimeMillis() - startTime)); + else + OLogManager.instance() + .debug(this, "--> OK, indexed %,d items in %,d ms", idxSize, (System.currentTimeMillis() - startTime)); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRecorder.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRecorder.java new file mode 100644 index 00000000000..df13c90c3c9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRecorder.java @@ -0,0 +1,417 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; + +@SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") +public class OIndexRecorder implements OIndex, OIndexInternal { + private static final Lock[] NO_LOCKS = new Lock[0]; + + private final OIndexInternal delegate; + + private final Set removedKeys = new HashSet(); + private final Map updatedKeys = new HashMap(); + + public OIndexRecorder(OIndexInternal delegate) { + this.delegate = delegate; + } + + public List getAffectedKeys() { + List result = new ArrayList(removedKeys.size() + updatedKeys.size()); + + for (Object key : removedKeys) { + result.add(copyKeyIfNeeded(key)); + } + for (Object key : updatedKeys.keySet()) { + result.add(copyKeyIfNeeded(key)); + } + + return result; + } + + private Object copyKeyIfNeeded(Object object) { + if (object instanceof ORecordId) + return new ORecordId((ORecordId) object); + else if (object instanceof OCompositeKey) { + final OCompositeKey copy = new OCompositeKey(); + for (Object key : ((OCompositeKey) object).getKeys()) { + copy.addKey(copyKeyIfNeeded(key)); + } + + return copy; + } + + return object; + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public OIndex create(String name, OIndexDefinition indexDefinition, String clusterIndexName, + Set clustersToIndex, boolean rebuild, OProgressListener progressListener) { + throw new UnsupportedOperationException("Not allowed operation."); + } + + @Override + public String getDatabaseName() { + return delegate.getDatabaseName(); + } + + @Override + public OType[] getKeyTypes() { + return delegate.getKeyTypes(); + } + + @Override + public OIdentifiable get(Object iKey) { + iKey = delegate.getCollatingValue(iKey); + + if (removedKeys.contains(iKey)) + return null; + + OIdentifiable updated = updatedKeys.get(iKey); + if (updated != null) + return updated; + + return delegate.get(iKey); + } + + @Override + public boolean contains(Object iKey) { + return get(iKey) != null; + } + + @Override + public OIndex put(Object iKey, OIdentifiable iValue) { + iKey = delegate.getCollatingValue(iKey); + + removedKeys.remove(iKey); + updatedKeys.put(iKey, iValue); + + return this; + } + + @Override + public boolean remove(Object key) { + key = delegate.getCollatingValue(key); + + removedKeys.add(key); + updatedKeys.remove(key); + + return false; + } + + @Override + public boolean remove(Object iKey, OIdentifiable iRID) { + iKey = delegate.getCollatingValue(iKey); + + removedKeys.add(iKey); + updatedKeys.remove(iKey); + + return false; + } + + @Override + public OIndex clear() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public long getSize() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public long count(final Object iKey) { + return get(iKey) != null ? 1l : 0l; + } + + @Override + public long getKeySize() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public ODocument checkEntry(OIdentifiable iRecord, Object iKey) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public int getVersion() { + return -1; + } + + @Override + public long getRebuildVersion() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void flush() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndex delete() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getType() { + return delegate.getType(); + } + + @Override + public boolean isAutomatic() { + return delegate.isAutomatic(); + } + + @Override + public long rebuild() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public long rebuild(OProgressListener iProgressListener) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public ODocument getConfiguration() { + return delegate.getConfiguration(); + } + + @Override + public OIndexInternal getInternal() { + return this; + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexDefinition getDefinition() { + return delegate.getDefinition(); + } + + @Override + public Set getClusters() { + return delegate.getClusters(); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexCursor cursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexCursor descCursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexKeyCursor keyCursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public ODocument getMetadata() { + return delegate.getMetadata(); + } + + @Override + public boolean supportsOrderedIterations() { + return delegate.supportsOrderedIterations(); + } + + @Override + public boolean isRebuilding() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public Object getFirstKey() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public Object getLastKey() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override public int getIndexId() { + return delegate.getIndexId(); + } + + @Override + public boolean isUnique() { + return delegate.isUnique(); + } + + @Override + public int compareTo(OIndex o) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public Object getCollatingValue(Object key) { + return delegate.getCollatingValue(key); + } + + @Override + public boolean loadFromConfiguration(ODocument iConfig) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public ODocument updateConfiguration() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndex addCluster(String iClusterName) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndex removeCluster(String iClusterName) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public boolean canBeUsedInEqualityOperators() { + return delegate.canBeUsedInEqualityOperators(); + } + + @Override + public boolean hasRangeQuerySupport() { + return delegate.hasRangeQuerySupport(); + } + + @Override + public void lockKeysForUpdate(Object... key) { + } + + @Override + public Lock[] lockKeysForUpdate(Collection keys) { + return NO_LOCKS; + } + + @Override + public void releaseKeysForUpdate(Object... key) { + } + + @Override + public void setType(OType type) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexMetadata loadMetadata(ODocument iConfig) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void setRebuildingFlag() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void close() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public String getAlgorithm() { + return delegate.getAlgorithm(); + } + + @Override + public void preCommit() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void addTxOperation(OTransactionIndexChanges changes) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void commit() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void postCommit() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public boolean acquireAtomicExclusiveLock(Object key) { + throw new UnsupportedOperationException("atomic locking is not supported by index recorder"); + } + + @Override + public String getIndexNameByKey(final Object key) { + return delegate.getName(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemote.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemote.java new file mode 100644 index 00000000000..7e5a8dfcd1c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemote.java @@ -0,0 +1,484 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQL; + +import java.util.*; + +/** + * Proxied abstract index. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public abstract class OIndexRemote implements OIndex { + public static final String QUERY_GET_VALUES_BEETWEN_SELECT = "select from index:%s where "; + public static final String QUERY_GET_VALUES_BEETWEN_INCLUSIVE_FROM_CONDITION = "key >= ?"; + public static final String QUERY_GET_VALUES_BEETWEN_EXCLUSIVE_FROM_CONDITION = "key > ?"; + public static final String QUERY_GET_VALUES_BEETWEN_INCLUSIVE_TO_CONDITION = "key <= ?"; + public static final String QUERY_GET_VALUES_BEETWEN_EXCLUSIVE_TO_CONDITION = "key < ?"; + public static final String QUERY_GET_VALUES_AND_OPERATOR = " and "; + public static final String QUERY_GET_VALUES_LIMIT = " limit "; + protected final static String QUERY_ENTRIES = "select key, rid from index:%s"; + protected final static String QUERY_ENTRIES_DESC = "select key, rid from index:%s order by key desc"; + + private final static String QUERY_GET_ENTRIES = "select from index:%s where key in [%s]"; + + private final static String QUERY_PUT = "insert into index:%s (key,rid) values (?,?)"; + private final static String QUERY_REMOVE = "delete from index:%s where key = ?"; + private final static String QUERY_REMOVE2 = "delete from index:%s where key = ? and rid = ?"; + private final static String QUERY_REMOVE3 = "delete from index:%s where rid = ?"; + private final static String QUERY_CONTAINS = "select count(*) as size from index:%s where key = ?"; + private final static String QUERY_COUNT = "select count(*) as size from index:%s where key = ?"; + private final static String QUERY_COUNT_RANGE = "select count(*) as size from index:%s where "; + private final static String QUERY_SIZE = "select count(*) as size from index:%s"; + private final static String QUERY_KEY_SIZE = "select count(distinct( key )) as size from index:%s"; + private final static String QUERY_KEYS = "select key from index:%s"; + private final static String QUERY_REBUILD = "rebuild index %s"; + private final static String QUERY_CLEAR = "delete from index:%s"; + private final static String QUERY_DROP = "drop index %s"; + protected final String databaseName; + private final String wrappedType; + private final String algorithm; + private final ORID rid; + protected OIndexDefinition indexDefinition; + protected String name; + protected ODocument configuration; + protected Set clustersToIndex; + + public OIndexRemote(final String iName, final String iWrappedType, final String algorithm, final ORID iRid, + final OIndexDefinition iIndexDefinition, final ODocument iConfiguration, final Set clustersToIndex) { + this.name = iName; + this.wrappedType = iWrappedType; + this.algorithm = algorithm; + this.rid = iRid; + this.indexDefinition = iIndexDefinition; + this.configuration = iConfiguration; + this.clustersToIndex = new HashSet(clustersToIndex); + this.databaseName = ODatabaseRecordThreadLocal.INSTANCE.get().getName(); + } + + public OIndexRemote create(final String name, final OIndexDefinition indexDefinition, final String clusterIndexName, + final Set clustersToIndex, boolean rebuild, final OProgressListener progressListener) { + this.name = name; + return this; + } + + public OIndexRemote delete() { + final OCommandRequest cmd = formatCommand(QUERY_DROP, name); + getDatabase().command(cmd).execute(); + return this; + } + + public String getDatabaseName() { + return databaseName; + } + + @Override + public long getRebuildVersion() { + throw new UnsupportedOperationException(); + } + + public boolean contains(final Object iKey) { + final OCommandRequest cmd = formatCommand(QUERY_CONTAINS, name); + final List result = getDatabase().command(cmd).execute(iKey); + return (Long) result.get(0).field("size") > 0; + } + + public long count(final Object iKey) { + final OCommandRequest cmd = formatCommand(QUERY_COUNT, name); + final List result = getDatabase().command(cmd).execute(iKey); + return (Long) result.get(0).field("size"); + } + + public long count(final Object iRangeFrom, final boolean iFromInclusive, final Object iRangeTo, final boolean iToInclusive, + final int maxValuesToFetch) { + final StringBuilder query = new StringBuilder(QUERY_COUNT_RANGE); + + if (iFromInclusive) + query.append(QUERY_GET_VALUES_BEETWEN_INCLUSIVE_FROM_CONDITION); + else + query.append(QUERY_GET_VALUES_BEETWEN_EXCLUSIVE_FROM_CONDITION); + + query.append(QUERY_GET_VALUES_AND_OPERATOR); + + if (iToInclusive) + query.append(QUERY_GET_VALUES_BEETWEN_INCLUSIVE_TO_CONDITION); + else + query.append(QUERY_GET_VALUES_BEETWEN_EXCLUSIVE_TO_CONDITION); + + if (maxValuesToFetch > 0) + query.append(QUERY_GET_VALUES_LIMIT).append(maxValuesToFetch); + + final OCommandRequest cmd = formatCommand(query.toString()); + return (Long) getDatabase().command(cmd).execute(iRangeFrom, iRangeTo); + } + + public OIndexRemote put(final Object iKey, final OIdentifiable iValue) { + if (iValue instanceof ORecord && !iValue.getIdentity().isValid()) + // SAVE IT BEFORE TO PUT + ((ORecord) iValue).save(); + + if (iValue.getIdentity().isNew()) + throw new OIndexException( + "Cannot insert values in manual indexes against remote protocol during a transaction. Temporary RID cannot be managed at server side"); + + final OCommandRequest cmd = formatCommand(QUERY_PUT, name); + getDatabase().command(cmd).execute(iKey, iValue.getIdentity()); + return this; + } + + public boolean remove(final Object key) { + final OCommandRequest cmd = formatCommand(QUERY_REMOVE, name); + return ((Integer) getDatabase().command(cmd).execute(key)) > 0; + } + + public boolean remove(final Object iKey, final OIdentifiable iRID) { + final int deleted; + if (iRID != null) { + + if (iRID.getIdentity().isNew()) + throw new OIndexException( + "Cannot remove values in manual indexes against remote protocol during a transaction. Temporary RID cannot be managed at server side"); + + final OCommandRequest cmd = formatCommand(QUERY_REMOVE2, name); + deleted = (Integer) getDatabase().command(cmd).execute(iKey, iRID); + } else { + final OCommandRequest cmd = formatCommand(QUERY_REMOVE, name); + deleted = (Integer) getDatabase().command(cmd).execute(iKey); + } + return deleted > 0; + } + + public int remove(final OIdentifiable iRecord) { + final OCommandRequest cmd = formatCommand(QUERY_REMOVE3, name, iRecord.getIdentity()); + return (Integer) getDatabase().command(cmd).execute(iRecord); + } + + public void automaticRebuild() { + throw new UnsupportedOperationException("autoRebuild()"); + } + + public long rebuild() { + final OCommandRequest cmd = formatCommand(QUERY_REBUILD, name); + return (Long) getDatabase().command(cmd).execute(); + } + + public OIndexRemote clear() { + final OCommandRequest cmd = formatCommand(QUERY_CLEAR, name); + getDatabase().command(cmd).execute(); + return this; + } + + public long getSize() { + final OCommandRequest cmd = formatCommand(QUERY_SIZE, name); + final List result = getDatabase().command(cmd).execute(); + return (Long) result.get(0).field("size"); + } + + public long getKeySize() { + final OCommandRequest cmd = formatCommand(QUERY_KEY_SIZE, name); + final List result = getDatabase().command(cmd).execute(); + return (Long) result.get(0).field("size"); + } + + public boolean isAutomatic() { + return indexDefinition != null && indexDefinition.getClassName() != null; + } + + @Override + public int getVersion() { + if (configuration == null) + return -1; + + final Integer version = configuration.field(OIndexInternal.INDEX_VERSION); + if (version != null) + return version; + + return -1; + } + + @Override + public boolean isUnique() { + return false; + } + + public String getName() { + return name; + } + + @Override + public void flush() { + } + + public String getType() { + return wrappedType; + } + + public String getAlgorithm() { + return algorithm; + } + + public ODocument getConfiguration() { + return configuration; + } + + @Override + public ODocument getMetadata() { + return configuration.field("metadata", OType.EMBEDDED); + } + + public ORID getIdentity() { + return rid; + } + + public void commit(final ODocument iDocument) { + } + + public OIndexInternal getInternal() { + return null; + } + + public long rebuild(final OProgressListener iProgressListener) { + return rebuild(); + } + + public OType[] getKeyTypes() { + if (indexDefinition != null) + return indexDefinition.getTypes(); + return new OType[0]; + } + + public Collection getEntries(final Collection iKeys) { + final StringBuilder params = new StringBuilder(128); + if (!iKeys.isEmpty()) { + params.append("?"); + for (int i = 1; i < iKeys.size(); i++) { + params.append(", ?"); + } + } + + final OCommandRequest cmd = formatCommand(QUERY_GET_ENTRIES, name, params.toString()); + return (Collection) getDatabase().command(cmd).execute(iKeys.toArray()); + } + + public OIndexDefinition getDefinition() { + return indexDefinition; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OIndexRemote that = (OIndexRemote) o; + + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + public Collection getEntries(final Collection iKeys, int maxEntriesToFetch) { + if (maxEntriesToFetch < 0) + return getEntries(iKeys); + + final StringBuilder params = new StringBuilder(128); + if (!iKeys.isEmpty()) { + params.append("?"); + for (int i = 1; i < iKeys.size(); i++) { + params.append(", ?"); + } + } + + final OCommandRequest cmd = formatCommand(QUERY_GET_ENTRIES + QUERY_GET_VALUES_LIMIT + maxEntriesToFetch, name, + params.toString()); + return getDatabase().command(cmd).execute(iKeys.toArray()); + } + + public Set getClusters() { + return Collections.unmodifiableSet(clustersToIndex); + } + + public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) { + return null; + } + + @Override + public boolean isRebuilding() { + return false; + } + + @Override + public Object getFirstKey() { + throw new UnsupportedOperationException("getFirstKey"); + } + + @Override + public Object getLastKey() { + throw new UnsupportedOperationException("getLastKey"); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + throw new UnsupportedOperationException("iterateEntriesBetween"); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + throw new UnsupportedOperationException("iterateEntriesMajor"); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + throw new UnsupportedOperationException("iterateEntriesMinor"); + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + throw new UnsupportedOperationException("iterateEntries"); + } + + @Override + public int getIndexId() { + throw new UnsupportedOperationException("getIndexId"); + } + + @Override + public OIndexCursor cursor() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES, name); + final Collection result = getDatabase().command(cmd).execute(); + + return new OIndexAbstractCursor() { + private final Iterator documentIterator = result.iterator(); + + @Override + public Map.Entry nextEntry() { + if (!documentIterator.hasNext()) + return null; + + final ODocument value = documentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return value.field("key"); + } + + @Override + public OIdentifiable getValue() { + return value.field("rid"); + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + }; + + } + + @Override + public OIndexCursor descCursor() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES_DESC, name); + final Collection result = getDatabase().command(cmd).execute(); + + return new OIndexAbstractCursor() { + private final Iterator documentIterator = result.iterator(); + + @Override + public Map.Entry nextEntry() { + if (!documentIterator.hasNext()) + return null; + + final ODocument value = documentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return value.field("key"); + } + + @Override + public OIdentifiable getValue() { + return value.field("rid"); + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + }; + } + + @Override + public OIndexKeyCursor keyCursor() { + final OCommandRequest cmd = formatCommand(QUERY_KEYS, name); + final Collection result = getDatabase().command(cmd).execute(); + + return new OIndexKeyCursor() { + private final Iterator documentIterator = result.iterator(); + + @Override + public Object next(int prefetchSize) { + if (!documentIterator.hasNext()) + return null; + + final ODocument value = documentIterator.next(); + + return value.field("key"); + } + }; + } + + @Override + public int compareTo(OIndex index) { + final String name = index.getName(); + return this.name.compareTo(name); + } + + protected OCommandRequest formatCommand(final String iTemplate, final Object... iArgs) { + final String text = String.format(iTemplate, iArgs); + return new OCommandSQL(text); + } + + protected ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteMultiValue.java new file mode 100644 index 00000000000..a8701607c58 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteMultiValue.java @@ -0,0 +1,109 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Proxied index. + * + * @author Luca Garulli + * + */ +@SuppressWarnings("unchecked") +public class OIndexRemoteMultiValue extends OIndexRemote> { + protected final static String QUERY_GET = "select EXPAND( rid ) from index:%s where key = ?"; + + public OIndexRemoteMultiValue(final String iName, final String iWrappedType, final String algorithm, final ORID iRid, + final OIndexDefinition iIndexDefinition, final ODocument iConfiguration, final Set clustersToIndex) { + super(iName, iWrappedType, algorithm, iRid, iIndexDefinition, iConfiguration, clustersToIndex); + } + + public Collection get(final Object iKey) { + final OCommandRequest cmd = formatCommand(QUERY_GET, name); + return new HashSet((Collection) getDatabase().command(cmd).execute(iKey)); + // return null; return (Collection) ((OStorageProxy) getDatabase().getStorage()).indexGet(name, iKey, null); + } + + public Iterator>> iterator() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES, name); + final Collection result = getDatabase().command(cmd).execute(); + + final Map> map = new LinkedHashMap>(); + for (final ODocument d : result) { + d.setLazyLoad(false); + Collection rids = map.get(d.field("key")); + if (rids == null) { + rids = new HashSet(); + map.put(d.field("key"), rids); + } + + rids.add((OIdentifiable) d.field("rid")); + } + + return map.entrySet().iterator(); + } + + public Iterator>> inverseIterator() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES, name); + final List result = getDatabase().command(cmd).execute(); + + final Map> map = new LinkedHashMap>(); + for (ListIterator it = result.listIterator(); it.hasPrevious();) { + ODocument d = it.previous(); + d.setLazyLoad(false); + Collection rids = map.get(d.field("key")); + if (rids == null) { + rids = new HashSet(); + map.put(d.field("key"), rids); + } + + rids.add((OIdentifiable) d.field("rid")); + } + + return map.entrySet().iterator(); + } + + public Iterator valuesIterator() { + throw new UnsupportedOperationException(); + } + + public Iterator valuesInverseIterator() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean supportsOrderedIterations() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteOneValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteOneValue.java new file mode 100644 index 00000000000..aec3e2ca7ea --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexRemoteOneValue.java @@ -0,0 +1,91 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Proxied single value index. + * + * @author Luca Garulli + * + */ +public class OIndexRemoteOneValue extends OIndexRemote { + protected final static String QUERY_GET = "select rid from index:%s where key = ?"; + + public OIndexRemoteOneValue(final String iName, final String iWrappedType, final String algorithm, final ORID iRid, + final OIndexDefinition iIndexDefinition, final ODocument iConfiguration, final Set clustersToIndex) { + super(iName, iWrappedType, algorithm, iRid, iIndexDefinition, iConfiguration, clustersToIndex); + } + + public OIdentifiable get(final Object iKey) { + final OCommandRequest cmd = formatCommand(QUERY_GET, name); + final List result = getDatabase().command(cmd).execute(iKey); + if (result != null && !result.isEmpty()) + return ((OIdentifiable) ((ODocument) result.get(0).getRecord()).field("rid")).getIdentity(); + return null; + // return (OIdentifiable) ((OStorageProxy) getDatabase().getStorage()).indexGet(name, iKey, null); + } + + public Iterator> iterator() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES, name); + final Collection result = getDatabase().command(cmd).execute(); + + final Map map = new LinkedHashMap(); + for (final ODocument d : result) { + d.setLazyLoad(false); + map.put(d.field("key"), (OIdentifiable) d.field("rid")); + } + + return map.entrySet().iterator(); + } + + public Iterator> inverseIterator() { + final OCommandRequest cmd = formatCommand(QUERY_ENTRIES, name); + final List result = getDatabase().command(cmd).execute(); + + final Map map = new LinkedHashMap(); + + for (ListIterator it = result.listIterator(); it.hasPrevious();) { + ODocument d = it.previous(); + d.setLazyLoad(false); + map.put(d.field("key"), (OIdentifiable) d.field("rid")); + } + + return map.entrySet().iterator(); + } + + @Override + public boolean isUnique() { + return true; + } + + @Override + public boolean supportsOrderedIterations() { + return false; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAware.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAware.java new file mode 100755 index 00000000000..75441d12256 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAware.java @@ -0,0 +1,319 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges.OPERATION; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey.OTransactionIndexEntry; +import com.orientechnologies.orient.core.tx.OTransactionRealAbstract; + +import java.util.Map.Entry; + +/** + * Transactional wrapper for indexes. Stores changes locally to the transaction until tx.commit(). All the other operations are + * delegated to the wrapped OIndex instance. + * + * @author Luca Garulli + */ +public abstract class OIndexTxAware extends OIndexAbstractDelegate { + private static final OAlwaysLessKey ALWAYS_LESS_KEY = new OAlwaysLessKey(); + private static final OAlwaysGreaterKey ALWAYS_GREATER_KEY = new OAlwaysGreaterKey(); + + protected ODatabaseDocumentInternal database; + + /** + * Indicates search behavior in case of {@link com.orientechnologies.orient.core.index.OCompositeKey} keys that have less amount + * of internal keys are used, whether lowest or highest partially matched key should be used. Such keys is allowed to use only in + */ + public static enum PartialSearchMode { + /** + * Any partially matched key will be used as search result. + */ + NONE, /** + * The biggest partially matched key will be used as search result. + */ + HIGHEST_BOUNDARY, + + /** + * The smallest partially matched key will be used as search result. + */ + LOWEST_BOUNDARY + } + + public OIndexTxAware(final ODatabaseDocumentInternal iDatabase, final OIndex iDelegate) { + super(iDelegate); + database = iDatabase; + } + + @Override + public long getSize() { + long tot = delegate.getSize(); + + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges != null) { + if (indexChanges.cleared) + // BEGIN FROM 0 + tot = 0; + + for (final Entry entry : indexChanges.changesPerKey.entrySet()) { + for (final OTransactionIndexEntry e : entry.getValue().entries) { + if (e.operation == OPERATION.REMOVE) { + if (e.value == null) + // KEY REMOVED + tot--; + } + } + } + + for (final OTransactionIndexEntry e : indexChanges.nullKeyChanges.entries) { + if (e.operation == OPERATION.REMOVE) { + if (e.value == null) + // KEY REMOVED + tot--; + } + } + } + + return tot; + } + + @Override + public OIndexTxAware put(Object iKey, final OIdentifiable iValue) { + checkForKeyType(iKey); + final ORID rid = iValue.getIdentity(); + + if (!rid.isValid()) + if (iValue instanceof ORecord) + // EARLY SAVE IT + ((ORecord) iValue).save(); + else + throw new IllegalArgumentException("Cannot store non persistent RID as index value for key '" + iKey + "'"); + + iKey = getCollatingValue(iKey); + + database.getTransaction().addIndexEntry(delegate, super.getName(), OPERATION.PUT, iKey, iValue); + return this; + } + + public OIndexTxAware putOnlyClientTrack(Object iKey, final OIdentifiable iValue) { + final ORID rid = iValue.getIdentity(); + + if (!rid.isValid()) + if (iValue instanceof ORecord) + // EARLY SAVE IT + ((ORecord) iValue).save(); + else + throw new IllegalArgumentException("Cannot store non persistent RID as index value for key '" + iKey + "'"); + + iKey = getCollatingValue(iKey); + + ((OTransactionRealAbstract) database.getTransaction()) + .addIndexEntry(delegate, super.getName(), OPERATION.PUT, iKey, iValue, true); + return this; + } + + @Override + public boolean remove(Object key) { + key = getCollatingValue(key); + database.getTransaction().addIndexEntry(delegate, super.getName(), OPERATION.REMOVE, key, null); + return true; + } + + @Override + public boolean remove(Object iKey, final OIdentifiable iRID) { + iKey = getCollatingValue(iKey); + database.getTransaction().addIndexEntry(delegate, super.getName(), OPERATION.REMOVE, iKey, iRID); + return true; + } + + public boolean removeOnlyClientTrack(Object iKey, final OIdentifiable iRID) { + iKey = getCollatingValue(iKey); + ((OTransactionRealAbstract) database.getTransaction()) + .addIndexEntry(delegate, super.getName(), OPERATION.REMOVE, iKey, iRID, true); + return true; + } + + @Override + public OIndexTxAware clear() { + database.getTransaction().addIndexEntry(delegate, super.getName(), OPERATION.CLEAR, null, null); + return this; + } + + @Override + public Object getFirstKey() { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return delegate.getFirstKey(); + + Object indexFirstKey; + if (indexChanges.cleared) + indexFirstKey = null; + else + indexFirstKey = delegate.getFirstKey(); + + Object firstKey = indexChanges.getFirstKey(); + while (true) { + OTransactionIndexChangesPerKey changesPerKey = indexChanges.getChangesPerKey(firstKey); + + for (OTransactionIndexEntry indexEntry : changesPerKey.entries) { + if (indexEntry.operation.equals(OPERATION.REMOVE)) + firstKey = null; + else + firstKey = changesPerKey.key; + } + + if (changesPerKey.key.equals(indexFirstKey)) + indexFirstKey = firstKey; + + if (firstKey != null) { + if (indexFirstKey != null && ((Comparable) indexFirstKey).compareTo(firstKey) < 0) + return indexFirstKey; + + return firstKey; + } + + firstKey = indexChanges.getHigherKey(changesPerKey.key); + if (firstKey == null) + return indexFirstKey; + } + } + + @Override + public Object getLastKey() { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return delegate.getLastKey(); + + Object indexLastKey; + if (indexChanges.cleared) + indexLastKey = null; + else + indexLastKey = delegate.getLastKey(); + + Object lastKey = indexChanges.getLastKey(); + while (true) { + OTransactionIndexChangesPerKey changesPerKey = indexChanges.getChangesPerKey(lastKey); + + for (OTransactionIndexEntry indexEntry : changesPerKey.entries) { + if (indexEntry.operation.equals(OPERATION.REMOVE)) + lastKey = null; + else + lastKey = changesPerKey.key; + } + + if (changesPerKey.key.equals(indexLastKey)) + indexLastKey = lastKey; + + if (lastKey != null) { + if (indexLastKey != null && ((Comparable) indexLastKey).compareTo(lastKey) > 0) + return indexLastKey; + + return lastKey; + } + + lastKey = indexChanges.getLowerKey(changesPerKey.key); + if (lastKey == null) + return indexLastKey; + } + } + + protected Object enhanceCompositeKey(Object key, PartialSearchMode partialSearchMode) { + if (!(key instanceof OCompositeKey)) + return key; + + final OCompositeKey compositeKey = (OCompositeKey) key; + final int keySize = getDefinition().getParamCount(); + + if (!(keySize == 1 || compositeKey.getKeys().size() == keySize || partialSearchMode.equals(PartialSearchMode.NONE))) { + final OCompositeKey fullKey = new OCompositeKey(compositeKey); + int itemsToAdd = keySize - fullKey.getKeys().size(); + + final Comparable keyItem; + if (partialSearchMode.equals(PartialSearchMode.HIGHEST_BOUNDARY)) + keyItem = ALWAYS_GREATER_KEY; + else + keyItem = ALWAYS_LESS_KEY; + + for (int i = 0; i < itemsToAdd; i++) + fullKey.addKey(keyItem); + + return fullKey; + } + + return key; + } + + protected Object enhanceToCompositeKeyBetweenAsc(Object keyTo, boolean toInclusive) { + PartialSearchMode partialSearchModeTo; + if (toInclusive) + partialSearchModeTo = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchModeTo = PartialSearchMode.LOWEST_BOUNDARY; + + keyTo = enhanceCompositeKey(keyTo, partialSearchModeTo); + return keyTo; + } + + protected Object enhanceFromCompositeKeyBetweenAsc(Object keyFrom, boolean fromInclusive) { + PartialSearchMode partialSearchModeFrom; + if (fromInclusive) + partialSearchModeFrom = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchModeFrom = PartialSearchMode.HIGHEST_BOUNDARY; + + keyFrom = enhanceCompositeKey(keyFrom, partialSearchModeFrom); + return keyFrom; + } + + protected Object enhanceToCompositeKeyBetweenDesc(Object keyTo, boolean toInclusive) { + PartialSearchMode partialSearchModeTo; + if (toInclusive) + partialSearchModeTo = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchModeTo = PartialSearchMode.LOWEST_BOUNDARY; + + keyTo = enhanceCompositeKey(keyTo, partialSearchModeTo); + return keyTo; + } + + protected Object enhanceFromCompositeKeyBetweenDesc(Object keyFrom, boolean fromInclusive) { + PartialSearchMode partialSearchModeFrom; + if (fromInclusive) + partialSearchModeFrom = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchModeFrom = PartialSearchMode.HIGHEST_BOUNDARY; + + keyFrom = enhanceCompositeKey(keyFrom, partialSearchModeFrom); + return keyFrom; + } + + protected Object getCollatingValue(final Object key) { + final OIndexDefinition definition = getDefinition(); + if (key != null && definition != null) + return definition.getCollate().transform(key); + return key; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareDictionary.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareDictionary.java new file mode 100644 index 00000000000..00ea80b265c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareDictionary.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Transactional wrapper for dictionary index. Stores changes locally to the transaction until tx.commit(). All the other operations + * are delegated to the wrapped OIndex instance. + * + * @author Luca Garulli + */ +public class OIndexTxAwareDictionary extends OIndexTxAwareOneValue { + public OIndexTxAwareDictionary(ODatabaseDocumentInternal iDatabase, OIndex iDelegate) { + super(iDatabase, iDelegate); + } + + @Override + public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareMultiValue.java new file mode 100755 index 00000000000..c797dcedcbd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareMultiValue.java @@ -0,0 +1,529 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges.OPERATION; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey.OTransactionIndexEntry; + +import java.util.*; + +/** + * Transactional wrapper for indexes. Stores changes locally to the transaction until tx.commit(). All the other operations are + * delegated to the wrapped OIndex instance. + * + * @author Luca Garulli + */ +public class OIndexTxAwareMultiValue extends OIndexTxAware> { + private static class MapEntry implements Map.Entry { + private final Object key; + private final OIdentifiable backendValue; + + public MapEntry(Object key, OIdentifiable backendValue) { + this.key = key; + this.backendValue = backendValue; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return backendValue; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + } + + private class PureTxBetweenIndexForwardCursor extends OIndexAbstractCursor { + private final OTransactionIndexChanges indexChanges; + private Object firstKey; + private Object lastKey; + + private Object nextKey; + + private Iterator valuesIterator = new OEmptyIterator(); + private Object key; + + public PureTxBetweenIndexForwardCursor(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + OTransactionIndexChanges indexChanges) { + this.indexChanges = indexChanges; + + fromKey = enhanceFromCompositeKeyBetweenAsc(fromKey, fromInclusive); + toKey = enhanceToCompositeKeyBetweenAsc(toKey, toInclusive); + + if (fromInclusive) + firstKey = indexChanges.getCeilingKey(fromKey); + else + firstKey = indexChanges.getHigherKey(fromKey); + + if (toInclusive) + lastKey = indexChanges.getFloorKey(toKey); + else + lastKey = indexChanges.getLowerKey(toKey); + + nextKey = firstKey; + } + + @Override + public Map.Entry nextEntry() { + if (valuesIterator.hasNext()) + return nextEntryInternal(); + + if (nextKey == null) + return null; + + Set result; + do { + result = calculateTxValue(nextKey, indexChanges); + key = nextKey; + + nextKey = indexChanges.getHigherKey(nextKey); + + if (nextKey != null && ODefaultComparator.INSTANCE.compare(nextKey, lastKey) > 0) + nextKey = null; + } while ((result == null || result.isEmpty()) && nextKey != null); + + if (result == null || result.isEmpty()) + return null; + + valuesIterator = result.iterator(); + return nextEntryInternal(); + } + + Map.Entry nextEntryInternal() { + final OIdentifiable identifiable = valuesIterator.next(); + return new Map.Entry() { + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return identifiable; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + } + + private class PureTxBetweenIndexBackwardCursor extends OIndexAbstractCursor { + private final OTransactionIndexChanges indexChanges; + private Object firstKey; + private Object lastKey; + + private Object nextKey; + + private Iterator valuesIterator = new OEmptyIterator(); + private Object key; + + public PureTxBetweenIndexBackwardCursor(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + OTransactionIndexChanges indexChanges) { + this.indexChanges = indexChanges; + + fromKey = enhanceFromCompositeKeyBetweenDesc(fromKey, fromInclusive); + toKey = enhanceToCompositeKeyBetweenDesc(toKey, toInclusive); + + if (fromInclusive) + firstKey = indexChanges.getCeilingKey(fromKey); + else + firstKey = indexChanges.getHigherKey(fromKey); + + if (toInclusive) + lastKey = indexChanges.getFloorKey(toKey); + else + lastKey = indexChanges.getLowerKey(toKey); + + nextKey = lastKey; + } + + @Override + public Map.Entry nextEntry() { + if (valuesIterator.hasNext()) + return nextEntryInternal(); + + if (nextKey == null) + return null; + + Set result; + do { + result = calculateTxValue(nextKey, indexChanges); + key = nextKey; + + nextKey = indexChanges.getLowerKey(nextKey); + + if (nextKey != null && ODefaultComparator.INSTANCE.compare(nextKey, firstKey) < 0) + nextKey = null; + } while ((result == null || result.isEmpty()) && nextKey != null); + + if (result == null || result.isEmpty()) + return null; + + valuesIterator = result.iterator(); + return nextEntryInternal(); + } + + private Map.Entry nextEntryInternal() { + final OIdentifiable identifiable = valuesIterator.next(); + return new Map.Entry() { + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return identifiable; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + } + + private class OIndexTxCursor extends OIndexAbstractCursor { + + private final OIndexCursor backedCursor; + private final boolean ascOrder; + private final OTransactionIndexChanges indexChanges; + private OIndexCursor txBetweenIndexCursor; + + private Map.Entry nextTxEntry; + private Map.Entry nextBackedEntry; + + private boolean firstTime; + + public OIndexTxCursor(OIndexCursor txCursor, OIndexCursor backedCursor, boolean ascOrder, + OTransactionIndexChanges indexChanges) { + this.backedCursor = backedCursor; + this.ascOrder = ascOrder; + this.indexChanges = indexChanges; + txBetweenIndexCursor = txCursor; + firstTime = true; + } + + @Override + public Map.Entry nextEntry() { + if (firstTime) { + nextTxEntry = txBetweenIndexCursor.nextEntry(); + nextBackedEntry = backedCursor.nextEntry(); + firstTime = false; + } + + Map.Entry result = null; + + while (result == null && (nextTxEntry != null || nextBackedEntry != null)) { + if (nextTxEntry == null && nextBackedEntry != null) { + result = nextBackedEntry(getPrefetchSize()); + } else if (nextBackedEntry == null && nextTxEntry != null) { + result = nextTxEntry(getPrefetchSize()); + } else if (nextTxEntry != null && nextBackedEntry != null) { + if (ascOrder) { + if (ODefaultComparator.INSTANCE.compare(nextBackedEntry.getKey(), nextTxEntry.getKey()) <= 0) { + result = nextBackedEntry(getPrefetchSize()); + } else { + result = nextTxEntry(getPrefetchSize()); + } + } else { + if (ODefaultComparator.INSTANCE.compare(nextBackedEntry.getKey(), nextTxEntry.getKey()) >= 0) { + result = nextBackedEntry(getPrefetchSize()); + } else { + result = nextTxEntry(getPrefetchSize()); + } + } + } + } + + return result; + } + + private Map.Entry nextTxEntry(int prefetchSize) { + Map.Entry result = nextTxEntry; + nextTxEntry = txBetweenIndexCursor.nextEntry(); + return result; + } + + private Map.Entry nextBackedEntry(int prefetchSize) { + Map.Entry result; + result = calculateTxIndexEntry(nextBackedEntry.getKey(), nextBackedEntry.getValue(), indexChanges); + nextBackedEntry = backedCursor.nextEntry(); + return result; + } + } + + public OIndexTxAwareMultiValue(final ODatabaseDocumentInternal database, final OIndex> delegate) { + super(database, delegate); + } + + @Override + public Set get(Object key) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.get(key); + + key = getCollatingValue(key); + + final Set result = new HashSet(); + if (!indexChanges.cleared) { + // BEGIN FROM THE UNDERLYING RESULT SET + final Collection subResult = super.get(key); + if (subResult != null) + for (OIdentifiable oid : subResult) + result.add(oid); + } + + final Set processed = new HashSet(); + for (OIdentifiable identifiable : result) { + Map.Entry entry = calculateTxIndexEntry(key, identifiable, indexChanges); + if (entry != null) + processed.add(entry.getValue()); + } + + Set txChanges = calculateTxValue(key, indexChanges); + if (txChanges != null) + processed.addAll(txChanges); + + if (!processed.isEmpty()) + return processed; + + return null; + } + + @Override + public boolean contains(final Object key) { + final Set result = get(key); + return result != null && !result.isEmpty(); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, final boolean fromInclusive, Object toKey, final boolean toInclusive, + final boolean ascOrder) { + + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + + fromKey = getCollatingValue(fromKey); + toKey = getCollatingValue(toKey); + + final OIndexCursor txCursor; + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(fromKey, fromInclusive, toKey, toInclusive, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(fromKey, fromInclusive, toKey, toInclusive, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + + fromKey = getCollatingValue(fromKey); + + final OIndexCursor txCursor; + + final Object lastKey = indexChanges.getLastKey(); + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(fromKey, fromInclusive, lastKey, true, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(fromKey, fromInclusive, lastKey, true, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesMinor(toKey, toInclusive, ascOrder); + + toKey = getCollatingValue(toKey); + + final OIndexCursor txCursor; + + final Object firstKey = indexChanges.getFirstKey(); + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(firstKey, true, toKey, toInclusive, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(firstKey, true, toKey, toInclusive, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesMinor(toKey, toInclusive, ascOrder); + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntries(keys, ascSortOrder); + + final List sortedKeys = new ArrayList(keys.size()); + for (Object key : keys) + sortedKeys.add(getCollatingValue(key)); + if (ascSortOrder) + Collections.sort(sortedKeys, ODefaultComparator.INSTANCE); + else + Collections.sort(sortedKeys, Collections.reverseOrder(ODefaultComparator.INSTANCE)); + + final OIndexCursor txCursor = new OIndexAbstractCursor() { + private Iterator keysIterator = sortedKeys.iterator(); + + private Iterator valuesIterator = new OEmptyIterator(); + private Object key; + + @Override + public Map.Entry nextEntry() { + if (valuesIterator.hasNext()) + return nextEntryInternal(); + + if (keysIterator == null) + return null; + + Set result = null; + + while (result == null && keysIterator.hasNext()) { + key = keysIterator.next(); + result = calculateTxValue(key, indexChanges); + + if (result != null && result.isEmpty()) + result = null; + } + + if (result == null) { + keysIterator = null; + return null; + } + + valuesIterator = result.iterator(); + return nextEntryInternal(); + } + + private Map.Entry nextEntryInternal() { + final OIdentifiable identifiable = valuesIterator.next(); + return new Map.Entry() { + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return identifiable; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + }; + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntries(keys, ascSortOrder); + return new OIndexTxCursor(txCursor, backedCursor, ascSortOrder, indexChanges); + } + + private Map.Entry calculateTxIndexEntry(final Object key, final OIdentifiable backendValue, + OTransactionIndexChanges indexChanges) { + final OTransactionIndexChangesPerKey changesPerKey = indexChanges.getChangesPerKey(key); + if (changesPerKey.entries.isEmpty()) + return createMapEntry(key, backendValue); + + int putCounter = 1; + for (OTransactionIndexEntry entry : changesPerKey.entries) { + if (entry.operation == OPERATION.PUT && entry.value.equals(backendValue)) + putCounter++; + else if (entry.operation == OPERATION.REMOVE) { + if (entry.value == null) + putCounter = 0; + else if (entry.value.equals(backendValue) && putCounter > 0) + putCounter--; + } + } + + if (putCounter <= 0) + return null; + + return createMapEntry(key, backendValue); + } + + private Map.Entry createMapEntry(final Object key, final OIdentifiable backendValue) { + return new MapEntry(key, backendValue); + } + + private Set calculateTxValue(final Object key, OTransactionIndexChanges indexChanges) { + final OTransactionIndexChangesPerKey changesPerKey = indexChanges.getChangesPerKey(key); + if (changesPerKey.entries.isEmpty()) + return null; + + final List result = new ArrayList(); + for (OTransactionIndexEntry entry : changesPerKey.entries) { + if (entry.operation == OPERATION.REMOVE) { + if (entry.value == null) + result.clear(); + else + result.remove(entry.value); + } else + result.add(entry.value); + } + + if (result.isEmpty()) + return null; + + return new HashSet(result); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareOneValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareOneValue.java new file mode 100755 index 00000000000..e1f1f431c9e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexTxAwareOneValue.java @@ -0,0 +1,433 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges; +import com.orientechnologies.orient.core.tx.OTransactionIndexChanges.OPERATION; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey.OTransactionIndexEntry; + +import java.util.*; + +/** + * Transactional wrapper for indexes. Stores changes locally to the transaction until tx.commit(). All the other operations are + * delegated to the wrapped OIndex instance. + * + * @author Luca Garulli + */ +public class OIndexTxAwareOneValue extends OIndexTxAware { + private static class MapEntry implements Map.Entry { + private final Object key; + private final OIdentifiable resultValue; + + public MapEntry(Object key, OIdentifiable resultValue) { + this.key = key; + this.resultValue = resultValue; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public OIdentifiable getValue() { + return resultValue; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + } + + private class PureTxBetweenIndexForwardCursor extends OIndexAbstractCursor { + private final OTransactionIndexChanges indexChanges; + private Object firstKey; + private Object lastKey; + + private Object nextKey; + + public PureTxBetweenIndexForwardCursor(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + OTransactionIndexChanges indexChanges) { + this.indexChanges = indexChanges; + + fromKey = enhanceFromCompositeKeyBetweenAsc(fromKey, fromInclusive); + toKey = enhanceToCompositeKeyBetweenAsc(toKey, toInclusive); + + if (toInclusive) + firstKey = indexChanges.getCeilingKey(fromKey); + else + firstKey = indexChanges.getHigherKey(fromKey); + + if (fromInclusive) + lastKey = indexChanges.getFloorKey(toKey); + else + lastKey = indexChanges.getLowerKey(toKey); + + nextKey = firstKey; + } + + @Override + public Map.Entry nextEntry() { + if (nextKey == null) + return null; + + Map.Entry result; + + do { + result = calculateTxIndexEntry(nextKey, null, indexChanges); + nextKey = indexChanges.getHigherKey(nextKey); + + if (nextKey != null && ODefaultComparator.INSTANCE.compare(nextKey, lastKey) > 0) + nextKey = null; + + } while (result == null && nextKey != null); + + return result; + } + } + + private class PureTxBetweenIndexBackwardCursor extends OIndexAbstractCursor { + private final OTransactionIndexChanges indexChanges; + private Object firstKey; + private Object lastKey; + + private Object nextKey; + + public PureTxBetweenIndexBackwardCursor(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + OTransactionIndexChanges indexChanges) { + this.indexChanges = indexChanges; + + fromKey = enhanceFromCompositeKeyBetweenDesc(fromKey, fromInclusive); + toKey = enhanceToCompositeKeyBetweenDesc(toKey, toInclusive); + + if (toInclusive) + firstKey = indexChanges.getCeilingKey(fromKey); + else + firstKey = indexChanges.getHigherKey(fromKey); + + if (fromInclusive) + lastKey = indexChanges.getFloorKey(toKey); + else + lastKey = indexChanges.getLowerKey(toKey); + + nextKey = lastKey; + } + + @Override + public Map.Entry nextEntry() { + if (nextKey == null) + return null; + + Map.Entry result; + do { + result = calculateTxIndexEntry(nextKey, null, indexChanges); + nextKey = indexChanges.getLowerKey(nextKey); + + if (nextKey != null && ODefaultComparator.INSTANCE.compare(nextKey, firstKey) < 0) + nextKey = null; + } while (result == null && nextKey != null); + + return result; + } + } + + private class OIndexTxCursor extends OIndexAbstractCursor { + + private final OIndexCursor backedCursor; + private final boolean ascOrder; + private final OTransactionIndexChanges indexChanges; + private OIndexCursor txBetweenIndexCursor; + + private Map.Entry nextTxEntry; + private Map.Entry nextBackedEntry; + + private boolean firstTime; + + public OIndexTxCursor(OIndexCursor txCursor, OIndexCursor backedCursor, boolean ascOrder, + OTransactionIndexChanges indexChanges) { + this.backedCursor = backedCursor; + this.ascOrder = ascOrder; + this.indexChanges = indexChanges; + txBetweenIndexCursor = txCursor; + firstTime = true; + } + + @Override + public Map.Entry nextEntry() { + if (firstTime) { + nextTxEntry = txBetweenIndexCursor.nextEntry(); + nextBackedEntry = backedCursor.nextEntry(); + firstTime = false; + } + + Map.Entry result = null; + + while (result == null && (nextTxEntry != null || nextBackedEntry != null)) { + if (nextTxEntry == null && nextBackedEntry != null) { + result = nextBackedEntry(getPrefetchSize()); + } else if (nextBackedEntry == null && nextTxEntry != null) { + result = nextTxEntry(getPrefetchSize()); + } else if (nextTxEntry != null && nextBackedEntry != null) { + if (ascOrder) { + if (ODefaultComparator.INSTANCE.compare(nextBackedEntry.getKey(), nextTxEntry.getKey()) <= 0) { + result = nextBackedEntry(getPrefetchSize()); + } else { + result = nextTxEntry(getPrefetchSize()); + } + } else { + if (ODefaultComparator.INSTANCE.compare(nextBackedEntry.getKey(), nextTxEntry.getKey()) >= 0) { + result = nextBackedEntry(getPrefetchSize()); + } else { + result = nextTxEntry(getPrefetchSize()); + } + } + } + } + + return result; + } + + private Map.Entry nextTxEntry(int prefetchSize) { + Map.Entry result = nextTxEntry; + nextTxEntry = txBetweenIndexCursor.nextEntry(); + return result; + } + + private Map.Entry nextBackedEntry(int prefetchSize) { + Map.Entry result; + result = calculateTxIndexEntry(nextBackedEntry.getKey(), nextBackedEntry.getValue(), indexChanges); + nextBackedEntry = backedCursor.nextEntry(); + return result; + } + } + + public OIndexTxAwareOneValue(final ODatabaseDocumentInternal iDatabase, final OIndex iDelegate) { + super(iDatabase, iDelegate); + } + + @Override + public ODocument checkEntry(final OIdentifiable iRecord, Object iKey) { + iKey = getCollatingValue(iKey); + + // CHECK IF ALREADY EXISTS IN TX + if (!database.getTransaction().isActive()) { + final OIdentifiable previousRecord = get(iKey); + if (previousRecord != null && !previousRecord.equals(iRecord)) { + final ODocument metadata = getMetadata(); + Boolean mergeKeys = false; + if (metadata != null) { + mergeKeys = metadata.field(OIndex.MERGE_KEYS); + } + final boolean mergeSameKey = mergeKeys != null && mergeKeys; + if (mergeSameKey) { + return (ODocument) previousRecord.getRecord(); + } else + throw new ORecordDuplicatedException(String + .format("Cannot index record %s: found duplicated key '%s' in index '%s' previously assigned to the record %s", + iRecord, iKey, getName(), previousRecord), getName(), previousRecord.getIdentity()); + } + return super.checkEntry(iRecord, iKey); + } + return null; + } + + @Override + public OIdentifiable get(Object key) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.get(key); + + key = getCollatingValue(key); + + OIdentifiable result; + if (!indexChanges.cleared) + // BEGIN FROM THE UNDERLYING RESULT SET + result = super.get(key); + else + // BEGIN FROM EMPTY RESULT SET + result = null; + + // FILTER RESULT SET WITH TRANSACTIONAL CHANGES + final Map.Entry entry = calculateTxIndexEntry(key, result, indexChanges); + if (entry == null) + return null; + + return entry.getValue(); + } + + @Override + public boolean contains(final Object key) { + return get(key) != null; + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, final boolean fromInclusive, Object toKey, final boolean toInclusive, + final boolean ascOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + + fromKey = getCollatingValue(fromKey); + toKey = getCollatingValue(toKey); + + final OIndexCursor txCursor; + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(fromKey, fromInclusive, toKey, toInclusive, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(fromKey, fromInclusive, toKey, toInclusive, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + + fromKey = getCollatingValue(fromKey); + + final OIndexCursor txCursor; + + final Object lastKey = indexChanges.getLastKey(); + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(fromKey, fromInclusive, lastKey, true, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(fromKey, fromInclusive, lastKey, true, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntriesMinor(toKey, toInclusive, ascOrder); + + toKey = getCollatingValue(toKey); + + final OIndexCursor txCursor; + + final Object firstKey = indexChanges.getFirstKey(); + if (ascOrder) + txCursor = new PureTxBetweenIndexForwardCursor(firstKey, true, toKey, toInclusive, indexChanges); + else + txCursor = new PureTxBetweenIndexBackwardCursor(firstKey, true, toKey, toInclusive, indexChanges); + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntriesMinor(toKey, toInclusive, ascOrder); + return new OIndexTxCursor(txCursor, backedCursor, ascOrder, indexChanges); + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + final OTransactionIndexChanges indexChanges = database.getTransaction().getIndexChanges(delegate.getName()); + if (indexChanges == null) + return super.iterateEntries(keys, ascSortOrder); + + final List sortedKeys = new ArrayList(keys.size()); + for (Object key : keys) + sortedKeys.add(getCollatingValue(key)); + + if (ascSortOrder) + Collections.sort(sortedKeys, ODefaultComparator.INSTANCE); + else + Collections.sort(sortedKeys, Collections.reverseOrder(ODefaultComparator.INSTANCE)); + + final OIndexCursor txCursor = new OIndexAbstractCursor() { + private Iterator keysIterator = sortedKeys.iterator(); + + @Override + public Map.Entry nextEntry() { + if (keysIterator == null) + return null; + + Map.Entry entry = null; + while (entry == null && keysIterator.hasNext()) { + final Object key = keysIterator.next(); + + entry = calculateTxIndexEntry(key, null, indexChanges); + } + + if (entry == null) { + keysIterator = null; + return null; + } + + return entry; + } + }; + + if (indexChanges.cleared) + return txCursor; + + final OIndexCursor backedCursor = super.iterateEntries(keys, ascSortOrder); + return new OIndexTxCursor(txCursor, backedCursor, ascSortOrder, indexChanges); + } + + private Map.Entry calculateTxIndexEntry(final Object key, final OIdentifiable backendValue, + final OTransactionIndexChanges indexChanges) { + final OTransactionIndexChangesPerKey changesPerKey = indexChanges.getChangesPerKey(key); + if (changesPerKey.entries.isEmpty()) { + if (backendValue == null) + return null; + else + return createMapEntry(key, backendValue); + } + + OIdentifiable result = backendValue; + + for (OTransactionIndexEntry entry : changesPerKey.entries) { + if (entry.operation == OPERATION.REMOVE) + result = null; + else if (entry.operation == OPERATION.PUT) + result = entry.value; + } + + if (result == null) + return null; + + final OIdentifiable resultValue = result; + return createMapEntry(key, resultValue); + } + + private Map.Entry createMapEntry(final Object key, final OIdentifiable resultValue) { + return new MapEntry(key, resultValue); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexUnique.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexUnique.java new file mode 100644 index 00000000000..074bfce4653 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexUnique.java @@ -0,0 +1,117 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey; + +/** + * Index implementation that allows only one value for a key. + * + * @author Luca Garulli + */ +public class OIndexUnique extends OIndexOneValue { + + private final OIndexEngine.Validator UNIQUE_VALIDATOR = new OIndexEngine.Validator() { + @Override + public Object validate(Object key, OIdentifiable oldValue, OIdentifiable newValue) { + if (oldValue != null) { + // CHECK IF THE ID IS THE SAME OF CURRENT: THIS IS THE UPDATE CASE + if (!oldValue.equals(newValue)) { + final Boolean mergeSameKey = metadata != null ? (Boolean) metadata.field(OIndex.MERGE_KEYS) : Boolean.FALSE; + if (mergeSameKey == null || !mergeSameKey) + throw new ORecordDuplicatedException(String + .format("Cannot index record %s: found duplicated key '%s' in index '%s' previously assigned to the record %s", + newValue.getIdentity(), key, getName(), oldValue.getIdentity()), getName(), oldValue.getIdentity()); + } else + return OIndexEngine.Validator.IGNORE; + } + + if (!newValue.getIdentity().isPersistent()) + newValue.getRecord().save(); + return newValue.getIdentity(); + } + }; + + public OIndexUnique(String name, String typeId, String algorithm, int version, OAbstractPaginatedStorage storage, + String valueContainerAlgorithm, ODocument metadata) { + super(name, typeId, algorithm, version, storage, valueContainerAlgorithm, metadata); + } + + @Override + public OIndexOneValue put(Object key, final OIdentifiable iSingleValue) { + if (iSingleValue != null && !iSingleValue.getIdentity().isPersistent()) + throw new IllegalArgumentException("Cannot index a non persistent record (" + iSingleValue.getIdentity() + ")"); + + key = getCollatingValue(key); + + final ODatabase database = getDatabase(); + final boolean txIsActive = database.getTransaction().isActive(); + + if (!txIsActive) { + keyLockManager.acquireExclusiveLock(key); + } + + try { + acquireSharedLock(); + try { + while (true) + try { + storage.validatedPutIndexValue(indexId, key, iSingleValue, UNIQUE_VALIDATOR); + break; + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + return this; + } finally { + releaseSharedLock(); + } + } finally { + if (!txIsActive) + keyLockManager.releaseExclusiveLock(key); + } + } + + @Override + public boolean canBeUsedInEqualityOperators() { + return true; + } + + @Override + public boolean supportsOrderedIterations() { + while (true) + try { + return storage.hasIndexRangeQuerySupport(indexId); + } catch (OInvalidIndexEngineIdException e) { + doReloadIndexEngine(); + } + } + + @Override + protected Iterable interpretTxKeyChanges( + OTransactionIndexChangesPerKey changes) { + return changes.interpret(OTransactionIndexChangesPerKey.Interpretation.Unique); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OIndexes.java b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexes.java new file mode 100755 index 00000000000..80aafd57bfd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OIndexes.java @@ -0,0 +1,225 @@ +/* + * Copyright 2012 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.util.OCollections; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.index.hashindex.local.OHashIndexFactory; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.*; + +import static com.orientechnologies.common.util.OClassLoaderHelper.lookupProviderWithOrientClassLoader; + +/** + * Utility class to create indexes. New OIndexFactory can be registered + *

      + *

      + * In order to be detected, factories must implement the {@link OIndexFactory} interface. + *

      + *

      + *

      + * In addition to implementing this interface datasouces should have a services file:
      + * META-INF/services/com.orientechnologies.orient.core.index.OIndexFactory + *

      + *

      + *

      + * The file should contain a single line which gives the full name of the implementing class. + *

      + *

      + *

      + * Example:
      + * org.mycompany.index.MyIndexFactory + *

      + * + * @author Johann Sorel (Geomatys) + */ +public final class OIndexes { + + private static Set FACTORIES = null; + private static final Set DYNAMIC_FACTORIES = Collections.synchronizedSet(new HashSet()); + private static ClassLoader orientClassLoader = OIndexes.class.getClassLoader(); + + private OIndexes() { + } + + /** + * Cache a set of all factories. we do not use the service loader directly since it is not concurrent. + * + * @return Set + */ + private static synchronized Set getFactories() { + if (FACTORIES == null) { + + final Iterator ite = lookupProviderWithOrientClassLoader(OIndexFactory.class, orientClassLoader); + + final Set factories = new HashSet(); + while (ite.hasNext()) { + factories.add(ite.next()); + } + factories.addAll(DYNAMIC_FACTORIES); + FACTORIES = Collections.unmodifiableSet(factories); + } + return FACTORIES; + } + + /** + * @return Iterator of all index factories + */ + public static Iterator getAllFactories() { + return getFactories().iterator(); + } + + /** + * Iterates on all factories and append all index types. + * + * @return Set of all index types. + */ + public static Set getIndexTypes() { + final Set types = new HashSet(); + final Iterator ite = getAllFactories(); + while (ite.hasNext()) { + types.addAll(ite.next().getTypes()); + } + return types; + } + + /** + * Iterates on all factories and append all index engines. + * + * @return Set of all index engines. + */ + public static Set getIndexEngines() { + final Set engines = new HashSet(); + final Iterator ite = getAllFactories(); + while (ite.hasNext()) { + engines.addAll(ite.next().getAlgorithms()); + } + return engines; + } + + public static OIndexFactory getFactory(String indexType, String algorithm) { + if (algorithm == null) + algorithm = chooseDefaultIndexAlgorithm(indexType); + + final Iterator ite = getAllFactories(); + + while (ite.hasNext()) { + final OIndexFactory factory = ite.next(); + if (factory.getTypes().contains(indexType) && factory.getAlgorithms().contains(algorithm)) { + return factory; + } + } + + throw new OIndexException("Index with type " + indexType + " and algorithm " + algorithm + " does not exist."); + } + + /** + * @param database + * @param name + * @param indexType + * index type + * @param algorithm + * @param valueContainerAlgorithm + * @return OIndexInternal + * @throws OConfigurationException + * if index creation failed + * @throws OIndexException + * if index type does not exist + */ + public static OIndexInternal createIndex(ODatabaseDocumentInternal database, String name, String indexType, String algorithm, + String valueContainerAlgorithm, ODocument metadata, int version) throws OConfigurationException, OIndexException { + if (indexType.equalsIgnoreCase(OClass.INDEX_TYPE.UNIQUE_HASH_INDEX.name()) + || indexType.equalsIgnoreCase(OClass.INDEX_TYPE.NOTUNIQUE_HASH_INDEX.name()) + || indexType.equalsIgnoreCase(OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.name())) + algorithm = OHashIndexFactory.HASH_INDEX_ALGORITHM; + + return findFactoryByAlgorithmAndType(algorithm, indexType).createIndex(name, database, indexType, algorithm, + valueContainerAlgorithm, metadata, version); + + } + + public static OIndexFactory findFactoryByAlgorithmAndType(final String algorithm, final String indexType) { + + for (OIndexFactory factory : getFactories()) { + if (indexType == null || indexType.isEmpty() + || (factory.getTypes().contains(indexType)) && factory.getAlgorithms().contains(algorithm)) { + return factory; + } + } + throw new OIndexException("Index type " + indexType + " is not supported by the engine '" + algorithm + "'. Types are " + + OCollections.toString(getIndexTypes())); + } + + public static OIndexEngine createIndexEngine(final String name, final String algorithm, final String type, + final Boolean durableInNonTxMode, final OStorage storage, final int version, final Map indexProperties, + ODocument metadata) { + + final OIndexFactory factory = findFactoryByAlgorithmAndType(algorithm, type); + + return factory.createIndexEngine(algorithm, name, durableInNonTxMode, storage, version, indexProperties); + } + + public static String chooseDefaultIndexAlgorithm(String type) { + String algorithm = null; + + if (OClass.INDEX_TYPE.DICTIONARY.name().equals(type) || OClass.INDEX_TYPE.FULLTEXT.name().equals(type) + || OClass.INDEX_TYPE.NOTUNIQUE.name().equals(type) || OClass.INDEX_TYPE.UNIQUE.name().equals(type)) { + algorithm = ODefaultIndexFactory.SBTREE_ALGORITHM; + } else if (OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.name().equals(type) + || OClass.INDEX_TYPE.FULLTEXT_HASH_INDEX.name().equals(type) || OClass.INDEX_TYPE.NOTUNIQUE_HASH_INDEX.name().equals(type) + || OClass.INDEX_TYPE.UNIQUE_HASH_INDEX.name().equals(type)) { + algorithm = OHashIndexFactory.HASH_INDEX_ALGORITHM; + } + return algorithm; + } + + /** + * Scans for factory plug-ins on the application class path. This method is needed because the application class path can + * theoretically change, or additional plug-ins may become available. Rather than re-scanning the classpath on every invocation of + * the API, the class path is scanned automatically only on the first invocation. Clients can call this method to prompt a + * re-scan. Thus this method need only be invoked by sophisticated applications which dynamically make new plug-ins available at + * runtime. + */ + public static synchronized void scanForPlugins() { + // clear cache, will cause a rescan on next getFactories call + FACTORIES = null; + } + + /** + * Register at runtime custom factories + * + * @param factory + */ + public static void registerFactory(OIndexFactory factory) { + DYNAMIC_FACTORIES.add(factory); + scanForPlugins(); + } + + /** + * Unregister custom factories + * + * @param factory + */ + public static void unregisterFactory(OIndexFactory factory) { + DYNAMIC_FACTORIES.remove(factory); + scanForPlugins(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/ONullOutputListener.java b/core/src/main/java/com/orientechnologies/orient/core/index/ONullOutputListener.java new file mode 100755 index 00000000000..a80f914578d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/ONullOutputListener.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.listener.OProgressListener; + +/** + * Progress listener with no output. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ONullOutputListener implements OProgressListener { + public final static ONullOutputListener INSTANCE = new ONullOutputListener(); + + @Override + public void onBegin(Object iTask, long iTotal, Object metadata) { + } + + @Override + public boolean onProgress(Object iTask, long iCounter, float iPercent) { + return true; + } + + @Override + public void onCompletition(Object iTask, boolean iSucceed) { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyIndexDefinition.java new file mode 100755 index 00000000000..4339d233c35 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyIndexDefinition.java @@ -0,0 +1,220 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLCreateIndex; + +import java.util.Collections; +import java.util.List; + +/** + * Index implementation bound to one schema class property. + * + */ +public class OPropertyIndexDefinition extends OAbstractIndexDefinition { + private static final long serialVersionUID = 7395728581151922197L; + protected String className; + protected String field; + protected OType keyType; + + public OPropertyIndexDefinition(final String iClassName, final String iField, final OType iType) { + super(); + className = iClassName; + field = iField; + keyType = iType; + } + + /** + * Constructor used for index unmarshalling. + */ + public OPropertyIndexDefinition() { + } + + public String getClassName() { + return className; + } + + public List getFields() { + return Collections.singletonList(field); + } + + public List getFieldsToIndex() { + if (collate == null || collate.getName().equals(ODefaultCollate.NAME)) + return Collections.singletonList(field); + + return Collections.singletonList(field + " collate " + collate.getName()); + } + + public Object getDocumentValueToIndex(final ODocument iDocument) { + if (OType.LINK.equals(keyType)) { + final OIdentifiable identifiable = iDocument.field(field); + if (identifiable != null) + return createValue(identifiable.getIdentity()); + else + return null; + } + return createValue(iDocument.field(field)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + if (!super.equals(o)) + return false; + + final OPropertyIndexDefinition that = (OPropertyIndexDefinition) o; + + if (!className.equals(that.className)) + return false; + if (!field.equals(that.field)) + return false; + if (keyType != that.keyType) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + className.hashCode(); + result = 31 * result + field.hashCode(); + result = 31 * result + keyType.hashCode(); + return result; + } + + @Override + public String toString() { + return "OPropertyIndexDefinition{" + "className='" + className + '\'' + ", field='" + field + '\'' + ", keyType=" + keyType + + ", collate=" + collate + ", null values ignored = " + isNullValuesIgnored() + '}'; + } + + public Object createValue(final List params) { + return OType.convert(params.get(0), keyType.getDefaultJavaType()); + } + + /** + * {@inheritDoc} + */ + public Object createValue(final Object... params) { + return OType.convert(params[0], keyType.getDefaultJavaType()); + } + + public int getParamCount() { + return 1; + } + + public OType[] getTypes() { + return new OType[] { keyType }; + } + + @Override + protected final void fromStream() { + serializeFromStream(); + } + + @Override + public final ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + + try { + serializeToStream(); + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + + return document; + } + + protected void serializeToStream() { + super.serializeToStream(); + + document.field("className", className); + document.field("field", field); + document.field("keyType", keyType.toString()); + document.field("collate", collate.getName()); + document.field("nullValuesIgnored", isNullValuesIgnored()); + } + + protected void serializeFromStream() { + super.serializeFromStream(); + + className = document.field("className"); + field = document.field("field"); + + final String keyTypeStr = document.field("keyType"); + keyType = OType.valueOf(keyTypeStr); + + setCollate((String) document.field("collate")); + setNullValuesIgnored(!Boolean.FALSE.equals(document. field("nullValuesIgnored"))); + } + + /** + * {@inheritDoc} + * + * @param indexName + * @param indexType + */ + public String toCreateIndexDDL(final String indexName, final String indexType, final String engine) { + return createIndexDDLWithFieldType(indexName, indexType, engine).toString(); + } + + protected StringBuilder createIndexDDLWithFieldType(String indexName, String indexType, String engine) { + final StringBuilder ddl = createIndexDDLWithoutFieldType(indexName, indexType,engine); + ddl.append(' ').append(keyType.name()); + return ddl; + } + + protected StringBuilder createIndexDDLWithoutFieldType(final String indexName, final String indexType,final String engine) { + final StringBuilder ddl = new StringBuilder("create index `"); + + final String shortName = className + "." + field; + if (indexName.equalsIgnoreCase(shortName)) { + ddl.append(shortName).append("` "); + } else { + ddl.append(indexName).append("` on `"); + ddl.append(className).append("` ( `").append(field).append("`"); + + if (!collate.getName().equals(ODefaultCollate.NAME)) + ddl.append(" collate ").append(collate.getName()); + + ddl.append(" ) "); + } + ddl.append(indexType); + + if (engine != null) + ddl.append(' ').append(OCommandExecutorSQLCreateIndex.KEYWORD_ENGINE + " " + engine); + return ddl; + } + + @Override + public boolean isAutomatic() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyListIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyListIndexDefinition.java new file mode 100755 index 00000000000..1fa209e77d6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyListIndexDefinition.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.*; + +/** + * Index implementation bound to one schema class property that presents + * {@link com.orientechnologies.orient.core.metadata.schema.OType#EMBEDDEDLIST}, + * {@link com.orientechnologies.orient.core.metadata.schema.OType#LINKLIST}, + * {@link com.orientechnologies.orient.core.metadata.schema.OType#LINKSET} or + * {@link com.orientechnologies.orient.core.metadata.schema.OType#EMBEDDEDSET} properties. + */ +public class OPropertyListIndexDefinition extends OAbstractIndexDefinitionMultiValue { + + private static final long serialVersionUID = -6499782365051906190L; + + public OPropertyListIndexDefinition(final String iClassName, final String iField, final OType iType) { + super(iClassName, iField, iType); + } + + public OPropertyListIndexDefinition() { + } + + @Override + public Object getDocumentValueToIndex(ODocument iDocument) { + return createValue(iDocument.field(field)); + } + + @Override + public Object createValue(List params) { + if (!(params.get(0) instanceof Collection)) + params = (List) Collections.singletonList(params); + + final Collection multiValueCollection = (Collection) params.get(0); + final List values = new ArrayList(multiValueCollection.size()); + for (final Object item : multiValueCollection) { + values.add(createSingleValue(item)); + } + return values; + } + + @Override + public Object createValue(final Object... params) { + if (!(params[0] instanceof Collection)) { + return null; + } + + final Collection multiValueCollection = (Collection) params[0]; + final List values = new ArrayList(multiValueCollection.size()); + for (final Object item : multiValueCollection) { + values.add(createSingleValue(item)); + } + return values; + } + + public Object createSingleValue(final Object... param) { + try { + return OType.convert(param[0], keyType.getDefaultJavaType()); + } catch (Exception e) { + OException ex = OException + .wrapException(new OIndexException("Invalid key for index: " + param[0] + " cannot be converted to " + keyType), e); + throw ex; + } + } + + public void processChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove) { + switch (changeEvent.getChangeType()) { + case ADD: { + processAdd(createSingleValue(changeEvent.getValue()), keysToAdd, keysToRemove); + break; + } + case REMOVE: { + processRemoval(createSingleValue(changeEvent.getOldValue()), keysToAdd, keysToRemove); + break; + } + case UPDATE: { + processRemoval(createSingleValue(changeEvent.getOldValue()), keysToAdd, keysToRemove); + processAdd(createSingleValue(changeEvent.getValue()), keysToAdd, keysToRemove); + break; + } + default: + throw new IllegalArgumentException("Invalid change type : " + changeEvent.getChangeType()); + } + } + + @Override + public String toCreateIndexDDL(String indexName, String indexType, String engine) { + return createIndexDDLWithoutFieldType(indexName, indexType, engine).toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyMapIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyMapIndexDefinition.java new file mode 100755 index 00000000000..66c20f791c5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyMapIndexDefinition.java @@ -0,0 +1,217 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.*; + +/** + * Index implementation bound to one schema class property that presents + * {@link com.orientechnologies.orient.core.metadata.schema.OType#EMBEDDEDMAP or + * + * @link com.orientechnologies.orient.core.metadata.schema.OType#LINKMAP} property. + */ +public class OPropertyMapIndexDefinition extends OAbstractIndexDefinitionMultiValue { + + private static final long serialVersionUID = 275241019910834466L; + + /** + * Indicates whether Map will be indexed using its keys or values. + */ + public static enum INDEX_BY { + KEY, VALUE + } + + private INDEX_BY indexBy = INDEX_BY.KEY; + + public OPropertyMapIndexDefinition() { + } + + public OPropertyMapIndexDefinition(final String iClassName, final String iField, final OType iType, final INDEX_BY indexBy) { + super(iClassName, iField, iType); + + if (indexBy == null) + throw new NullPointerException("You have to provide way by which map entries should be mapped"); + + this.indexBy = indexBy; + } + + @Override + public Object getDocumentValueToIndex(ODocument iDocument) { + return createValue(iDocument.field(field)); + } + + @Override + public Object createValue(List params) { + if (!(params.get(0) instanceof Map)) + return null; + + final Collection mapParams = extractMapParams((Map) params.get(0)); + final List result = new ArrayList(mapParams.size()); + for (final Object mapParam : mapParams) { + result.add(createSingleValue(mapParam)); + } + + return result; + } + + @Override + public Object createValue(Object... params) { + if (!(params[0] instanceof Map)) + return null; + + final Collection mapParams = extractMapParams((Map) params[0]); + + final List result = new ArrayList(mapParams.size()); + for (final Object mapParam : mapParams) { + result.add(createSingleValue(mapParam)); + } + + return result; + } + + public INDEX_BY getIndexBy() { + return indexBy; + } + + @Override + protected void serializeToStream() { + super.serializeToStream(); + document.field("mapIndexBy", indexBy.toString()); + } + + @Override + protected void serializeFromStream() { + super.serializeFromStream(); + indexBy = INDEX_BY.valueOf(document. field("mapIndexBy")); + } + + private Collection extractMapParams(Map map) { + if (indexBy == INDEX_BY.KEY) + return map.keySet(); + + return map.values(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + OPropertyMapIndexDefinition that = (OPropertyMapIndexDefinition) o; + + if (indexBy != that.indexBy) + return false; + + return true; + } + + public Object createSingleValue(final Object... param) { + return OType.convert(param[0], keyType.getDefaultJavaType()); + } + + public void processChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove) { + final boolean result; + if (indexBy.equals(INDEX_BY.KEY)) + result = processKeyChangeEvent(changeEvent, keysToAdd, keysToRemove); + else + result = processValueChangeEvent(changeEvent, keysToAdd, keysToRemove); + + if (!result) + throw new IllegalArgumentException("Invalid change type :" + changeEvent.getChangeType()); + } + + private boolean processKeyChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove) { + switch (changeEvent.getChangeType()) { + case ADD: + processAdd(createSingleValue(changeEvent.getKey()), keysToAdd, keysToRemove); + return true; + case REMOVE: + processRemoval(createSingleValue(changeEvent.getKey()), keysToAdd, keysToRemove); + return true; + case UPDATE: + return true; + } + return false; + } + + private boolean processValueChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove) { + switch (changeEvent.getChangeType()) { + case ADD: + processAdd(createSingleValue(changeEvent.getValue()), keysToAdd, keysToRemove); + return true; + case REMOVE: + processRemoval(createSingleValue(changeEvent.getOldValue()), keysToAdd, keysToRemove); + return true; + case UPDATE: + processRemoval(createSingleValue(changeEvent.getOldValue()), keysToAdd, keysToRemove); + processAdd(createSingleValue(changeEvent.getValue()), keysToAdd, keysToRemove); + return true; + } + return false; + } + + @Override + public List getFieldsToIndex() { + if (indexBy == INDEX_BY.KEY) + return Collections.singletonList(field + " by key"); + return Collections.singletonList(field + " by value"); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + indexBy.hashCode(); + return result; + } + + @Override + public String toString() { + return "OPropertyMapIndexDefinition{" + "indexBy=" + indexBy + "} " + super.toString(); + } + + @Override + public String toCreateIndexDDL(String indexName, String indexType,String engine) { + final StringBuilder ddl = new StringBuilder("create index `"); + + ddl.append(indexName).append("` on `"); + ddl.append(className).append("` ( `").append(field).append("`"); + + if (indexBy == INDEX_BY.KEY) + ddl.append(" by key"); + else + ddl.append(" by value"); + + ddl.append(" ) "); + ddl.append(indexType); + + return ddl.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyRidBagIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyRidBagIndexDefinition.java new file mode 100755 index 00000000000..e4aaa302b1b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OPropertyRidBagIndexDefinition.java @@ -0,0 +1,107 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Index definition for index which is bound to field with type {@link OType#LINKBAG} . + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 1/30/14 + */ +public class OPropertyRidBagIndexDefinition extends OAbstractIndexDefinitionMultiValue { + private static final long serialVersionUID = -8315498456603024776L; + + public OPropertyRidBagIndexDefinition() { + } + + public OPropertyRidBagIndexDefinition(String className, String field) { + super(className, field, OType.LINK); + } + + @Override + public Object createSingleValue(Object... param) { + return OType.convert(param[0], keyType.getDefaultJavaType()); + } + + public void processChangeEvent(final OMultiValueChangeEvent changeEvent, final Map keysToAdd, + final Map keysToRemove) { + switch (changeEvent.getChangeType()) { + case ADD: { + processAdd(createSingleValue(changeEvent.getValue()), keysToAdd, keysToRemove); + break; + } + case REMOVE: { + processRemoval(createSingleValue(changeEvent.getOldValue()), keysToAdd, keysToRemove); + break; + } + default: + throw new IllegalArgumentException("Invalid change type : " + changeEvent.getChangeType()); + } + } + + @Override + public Object getDocumentValueToIndex(ODocument iDocument) { + return createValue(iDocument.field(field)); + } + + @Override + public Object createValue(final List params) { + if (!(params.get(0) instanceof ORidBag)) + return null; + + final ORidBag ridBag = (ORidBag) params.get(0); + final List values = new ArrayList(); + for (final OIdentifiable item : ridBag) { + values.add(createSingleValue(item)); + } + + return values; + } + + @Override + public Object createValue(final Object... params) { + if (!(params[0] instanceof ORidBag)) + return null; + + final ORidBag ridBag = (ORidBag) params[0]; + final List values = new ArrayList(); + for (final OIdentifiable item : ridBag) { + values.add(createSingleValue(item)); + } + + return values; + } + + @Override + public String toCreateIndexDDL(String indexName, String indexType,String engine) { + return createIndexDDLWithoutFieldType(indexName, indexType,engine).toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/ORuntimeKeyIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/ORuntimeKeyIndexDefinition.java new file mode 100755 index 00000000000..0f555b4a983 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/ORuntimeKeyIndexDefinition.java @@ -0,0 +1,177 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Index definition that use the serializer specified at run-time not based on type. This is useful to have custom type keys for + * indexes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED") +public class ORuntimeKeyIndexDefinition extends OAbstractIndexDefinition { + private static final long serialVersionUID = -8855918974071833818L; + private transient OBinarySerializer serializer; + + @SuppressWarnings("unchecked") + public ORuntimeKeyIndexDefinition(final byte iId, int version) { + super(); + + serializer = (OBinarySerializer) OBinarySerializerFactory.getInstance().getObjectSerializer(iId); + if (serializer == null) + throw new OConfigurationException("Runtime index definition cannot find binary serializer with id=" + iId + + ". Assure to plug custom serializer into the server."); + } + + public ORuntimeKeyIndexDefinition() { + } + + public List getFields() { + return Collections.emptyList(); + } + + public List getFieldsToIndex() { + return Collections.emptyList(); + } + + public String getClassName() { + return null; + } + + public Comparable createValue(final List params) { + return (Comparable) params.get(0); + } + + public Comparable createValue(final Object... params) { + return createValue(Arrays.asList(params)); + } + + public int getParamCount() { + return 1; + } + + public OType[] getTypes() { + return new OType[0]; + } + + @Override + public ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + serializeToStream(); + return document; + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + + @Override + protected void serializeToStream() { + super.serializeToStream(); + + document.field("keySerializerId", serializer.getId()); + document.field("collate", collate.getName()); + document.field("nullValuesIgnored", isNullValuesIgnored()); + } + + @SuppressWarnings("unchecked") + @Override + protected void fromStream() { + serializeFromStream(); + } + + @Override + protected void serializeFromStream() { + super.serializeFromStream(); + + final byte keySerializerId = ((Number) document.field("keySerializerId")).byteValue(); + serializer = (OBinarySerializer) OBinarySerializerFactory.getInstance().getObjectSerializer(keySerializerId); + if (serializer == null) + throw new OConfigurationException("Runtime index definition cannot find binary serializer with id=" + keySerializerId + + ". Assure to plug custom serializer into the server."); + + String collateField = document.field("collate"); + if (collateField == null) + collateField = ODefaultCollate.NAME; + setNullValuesIgnored(!Boolean.FALSE.equals(document. field("nullValuesIgnored"))); + } + + public Object getDocumentValueToIndex(final ODocument iDocument) { + throw new OIndexException("This method is not supported in given index definition."); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final ORuntimeKeyIndexDefinition that = (ORuntimeKeyIndexDefinition) o; + return serializer.equals(that.serializer); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + serializer.getId(); + return result; + } + + @Override + public String toString() { + return "ORuntimeKeyIndexDefinition{" + "serializer=" + serializer.getId() + '}'; + } + + /** + * {@inheritDoc} + * + * @param indexName + * @param indexType + */ + public String toCreateIndexDDL(final String indexName, final String indexType, String engine) { + final StringBuilder ddl = new StringBuilder("create index `"); + ddl.append(indexName).append("` ").append(indexType).append(' '); + ddl.append("runtime ").append(serializer.getId()); + return ddl.toString(); + } + + public OBinarySerializer getSerializer() { + return serializer; + } + + @Override + public boolean isAutomatic() { + return getClassName() != null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/OSimpleKeyIndexDefinition.java b/core/src/main/java/com/orientechnologies/orient/core/index/OSimpleKeyIndexDefinition.java new file mode 100755 index 00000000000..e8266085675 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/OSimpleKeyIndexDefinition.java @@ -0,0 +1,233 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OSQLEngine; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class OSimpleKeyIndexDefinition extends OAbstractIndexDefinition { + private static final long serialVersionUID = -1264300379465791244L; + private OType[] keyTypes; + + public OSimpleKeyIndexDefinition(int version, final OType... keyTypes) { + super(); + + this.keyTypes = keyTypes; + } + + public OSimpleKeyIndexDefinition() { + } + + public OSimpleKeyIndexDefinition(OType[] keyTypes2, List collatesList, int version) { + super(); + + this.keyTypes = Arrays.copyOf(keyTypes2, keyTypes2.length); + + if (keyTypes.length > 1) { + OCompositeCollate collate = new OCompositeCollate(this); + if (collatesList != null) { + for (OCollate oCollate : collatesList) { + collate.addCollate(oCollate); + } + } else { + final int typesSize = keyTypes.length; + final OCollate defCollate = OSQLEngine.getCollate(ODefaultCollate.NAME); + for (int i = 0; i < typesSize; i++) { + collate.addCollate(defCollate); + } + } + this.collate = collate; + } + + } + + public List getFields() { + return Collections.emptyList(); + } + + public List getFieldsToIndex() { + return Collections.emptyList(); + } + + public String getClassName() { + return null; + } + + public Object createValue(final List params) { + return createValue(params != null ? params.toArray() : null); + } + + public Object createValue(final Object... params) { + if (params == null || params.length == 0) + return null; + + if (keyTypes.length == 1) + return OType.convert(params[0], keyTypes[0].getDefaultJavaType()); + + final OCompositeKey compositeKey = new OCompositeKey(); + + for (int i = 0; i < params.length; ++i) { + final Comparable paramValue = (Comparable) OType.convert(params[i], keyTypes[i].getDefaultJavaType()); + + if (paramValue == null) + return null; + compositeKey.addKey(paramValue); + } + + return compositeKey; + } + + public int getParamCount() { + return keyTypes.length; + } + + public OType[] getTypes() { + return Arrays.copyOf(keyTypes, keyTypes.length); + } + + @Override + public ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + serializeToStream(); + return document; + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + + @Override + protected void serializeToStream() { + super.serializeToStream(); + + final List keyTypeNames = new ArrayList(keyTypes.length); + + for (final OType keyType : keyTypes) + keyTypeNames.add(keyType.toString()); + + document.field("keyTypes", keyTypeNames, OType.EMBEDDEDLIST); + if (collate instanceof OCompositeCollate) { + List collatesNames = new ArrayList(); + for (OCollate curCollate : ((OCompositeCollate) this.collate).getCollates()) + collatesNames.add(curCollate.getName()); + document.field("collates", collatesNames, OType.EMBEDDEDLIST); + } else + document.field("collate", collate.getName()); + + document.field("nullValuesIgnored", isNullValuesIgnored()); + } + + @Override + protected void fromStream() { + serializeFromStream(); + } + + @Override + protected void serializeFromStream() { + super.serializeFromStream(); + + final List keyTypeNames = document.field("keyTypes"); + keyTypes = new OType[keyTypeNames.size()]; + + int i = 0; + for (final String keyTypeName : keyTypeNames) { + keyTypes[i] = OType.valueOf(keyTypeName); + i++; + } + String collate = document.field("collate"); + if (collate != null) { + setCollate(collate); + } else { + final List collatesNames = document.field("collates"); + if( collatesNames != null ) { + OCompositeCollate collates = new OCompositeCollate(this); + for (String collateName : collatesNames) + collates.addCollate(OSQLEngine.getCollate(collateName)); + this.collate = collates; + } + } + + setNullValuesIgnored(!Boolean.FALSE.equals(document.field("nullValuesIgnored"))); + } + + public Object getDocumentValueToIndex(final ODocument iDocument) { + throw new OIndexException("This method is not supported in given index definition."); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final OSimpleKeyIndexDefinition that = (OSimpleKeyIndexDefinition) o; + if (!Arrays.equals(keyTypes, that.keyTypes)) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (keyTypes != null ? Arrays.hashCode(keyTypes) : 0); + return result; + } + + @Override + public String toString() { + return "OSimpleKeyIndexDefinition{" + "keyTypes=" + (keyTypes == null ? null : Arrays.asList(keyTypes)) + '}'; + } + + /** + * {@inheritDoc} + * + * @param indexName + * @param indexType + */ + public String toCreateIndexDDL(final String indexName, final String indexType, final String engine) { + final StringBuilder ddl = new StringBuilder("create index `"); + ddl.append(indexName).append("` ").append(indexType).append(' '); + + if (keyTypes != null && keyTypes.length > 0) { + ddl.append(keyTypes[0].toString()); + for (int i = 1; i < keyTypes.length; i++) { + ddl.append(", ").append(keyTypes[i].toString()); + } + } + return ddl.toString(); + } + + @Override + public boolean isAutomatic() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/engine/OHashTableIndexEngine.java b/core/src/main/java/com/orientechnologies/orient/core/index/engine/OHashTableIndexEngine.java new file mode 100755 index 00000000000..e00b41e41b5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/engine/OHashTableIndexEngine.java @@ -0,0 +1,435 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.engine; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.index.hashindex.local.*; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin + * @since 15.07.13 + */ +public final class OHashTableIndexEngine implements OIndexEngine { + public static final int VERSION = 2; + + public static final String METADATA_FILE_EXTENSION = ".him"; + public static final String TREE_FILE_EXTENSION = ".hit"; + public static final String BUCKET_FILE_EXTENSION = ".hib"; + public static final String NULL_BUCKET_FILE_EXTENSION = ".hnb"; + + private final OHashTable hashTable; + private final OMurmurHash3HashFunction hashFunction; + + private int version; + + private final String name; + + public OHashTableIndexEngine(String name, Boolean durableInNonTxMode, OAbstractPaginatedStorage storage, int version) { + hashFunction = new OMurmurHash3HashFunction(); + + boolean durableInNonTx; + if (durableInNonTxMode == null) + durableInNonTx = OGlobalConfiguration.INDEX_DURABLE_IN_NON_TX_MODE.getValueAsBoolean(); + else + durableInNonTx = durableInNonTxMode; + + this.version = version; + if (version < 2) + hashTable = new OLocalHashTable20(name, METADATA_FILE_EXTENSION, TREE_FILE_EXTENSION, BUCKET_FILE_EXTENSION, + NULL_BUCKET_FILE_EXTENSION, hashFunction, durableInNonTx, storage); + else + hashTable = new OLocalHashTable(name, METADATA_FILE_EXTENSION, TREE_FILE_EXTENSION, BUCKET_FILE_EXTENSION, + NULL_BUCKET_FILE_EXTENSION, hashFunction, durableInNonTx, storage); + + this.name = name; + } + + @Override + public void init(String indexName, String indexType, OIndexDefinition indexDefinition, boolean isAutomatic, ODocument metadata) { + } + + @Override + public String getName() { + return name; + } + + @Override + public void create(OBinarySerializer valueSerializer, boolean isAutomatic, OType[] keyTypes, boolean nullPointerSupport, + OBinarySerializer keySerializer, int keySize, Set clustersToIndex, Map engineProperties, + ODocument metadata) { + hashFunction.setValueSerializer(keySerializer); + + hashTable.create(keySerializer, valueSerializer, keyTypes, nullPointerSupport); + } + + @Override + public void flush() { + } + + @Override + public void deleteWithoutLoad(String indexName) { + hashTable.deleteWithoutLoad(indexName, (OAbstractPaginatedStorage) getDatabase().getStorage().getUnderlying()); + } + + @Override + public String getIndexNameByKey(final Object key) { + return name; + } + + @Override + public void delete() { + hashTable.delete(); + } + + @Override + public void load(String indexName, OBinarySerializer valueSerializer, boolean isAutomatic, OBinarySerializer keySerializer, + OType[] keyTypes, boolean nullPointerSupport, int keySize, Map engineProperties) { + hashTable.load(indexName, keyTypes, nullPointerSupport); + hashFunction.setValueSerializer(hashTable.getKeySerializer()); + } + + @Override + public boolean contains(Object key) { + return hashTable.get(key) != null; + } + + @Override + public boolean remove(Object key) { + return hashTable.remove(key) != null; + } + + @Override + public void clear() { + hashTable.clear(); + } + + @Override + public void close() { + hashTable.close(); + } + + @Override + public Object get(Object key) { + return hashTable.get(key); + } + + @Override + public void put(Object key, Object value) { + hashTable.put(key, value); + } + + @SuppressWarnings("unchecked") + @Override + public boolean validatedPut(Object key, OIdentifiable value, Validator validator) { + return hashTable.validatedPut(key, value, (Validator) validator); + } + + @Override + public long size(ValuesTransformer transformer) { + if (transformer == null) + return hashTable.size(); + else { + long counter = 0; + + if (hashTable.isNullKeyIsSupported()) { + final Object nullValue = hashTable.get(null); + if (nullValue != null) { + counter += transformer.transformFromValue(nullValue).size(); + } + } + + OHashIndexBucket.Entry firstEntry = hashTable.firstEntry(); + if (firstEntry == null) + return counter; + + OHashIndexBucket.Entry[] entries = hashTable.ceilingEntries(firstEntry.key); + + while (entries.length > 0) { + for (OHashIndexBucket.Entry entry : entries) + counter += transformer.transformFromValue(entry.value).size(); + + entries = hashTable.higherEntries(entries[entries.length - 1].key); + } + + return counter; + } + } + + @Override + public int getVersion() { + return version; + } + + @Override + public boolean hasRangeQuerySupport() { + return false; + } + + @Override + public OIndexCursor iterateEntriesBetween(Object rangeFrom, boolean fromInclusive, Object rangeTo, boolean toInclusive, + boolean ascSortOrder, ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesBetween"); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean isInclusive, boolean ascSortOrder, + ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesMajor"); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean isInclusive, boolean ascSortOrder, ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesMinor"); + } + + @Override + public Object getFirstKey() { + throw new UnsupportedOperationException("firstKey"); + } + + @Override + public Object getLastKey() { + throw new UnsupportedOperationException("lastKey"); + } + + @Override + public OIndexCursor cursor(final ValuesTransformer valuesTransformer) { + return new OIndexAbstractCursor() { + private int nextEntriesIndex; + private OHashIndexBucket.Entry[] entries; + + private Iterator currentIterator = new OEmptyIterator(); + private Object currentKey; + + { + OHashIndexBucket.Entry firstEntry = hashTable.firstEntry(); + if (firstEntry == null) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else + entries = hashTable.ceilingEntries(firstEntry.key); + + if (entries.length == 0) + currentIterator = null; + } + + @Override + public Map.Entry nextEntry() { + if (currentIterator == null) + return null; + + if (currentIterator.hasNext()) + return nextCursorValue(); + + while (currentIterator != null && !currentIterator.hasNext()) { + if (entries.length == 0) { + currentIterator = null; + return null; + } + + final OHashIndexBucket.Entry bucketEntry = entries[nextEntriesIndex]; + + currentKey = bucketEntry.key; + + Object value = bucketEntry.value; + if (valuesTransformer != null) + currentIterator = valuesTransformer.transformFromValue(value).iterator(); + else + currentIterator = Collections.singletonList((OIdentifiable) value).iterator(); + + nextEntriesIndex++; + + if (nextEntriesIndex >= entries.length) { + entries = hashTable.higherEntries(entries[entries.length - 1].key); + + nextEntriesIndex = 0; + } + } + + if (currentIterator != null && !currentIterator.hasNext()) + return nextCursorValue(); + + currentIterator = null; + return null; + } + + private Map.Entry nextCursorValue() { + final OIdentifiable identifiable = currentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return currentKey; + } + + @Override + public OIdentifiable getValue() { + return identifiable; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public OIndexCursor descCursor(final ValuesTransformer valuesTransformer) { + return new OIndexAbstractCursor() { + private int nextEntriesIndex; + private OHashIndexBucket.Entry[] entries; + + private Iterator currentIterator = new OEmptyIterator(); + private Object currentKey; + + { + OHashIndexBucket.Entry lastEntry = hashTable.lastEntry(); + if (lastEntry == null) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else + entries = hashTable.floorEntries(lastEntry.key); + + if (entries.length == 0) + currentIterator = null; + } + + @Override + public Map.Entry nextEntry() { + if (currentIterator == null) + return null; + + if (currentIterator.hasNext()) + return nextCursorValue(); + + while (currentIterator != null && !currentIterator.hasNext()) { + if (entries.length == 0) { + currentIterator = null; + return null; + } + + final OHashIndexBucket.Entry bucketEntry = entries[nextEntriesIndex]; + + currentKey = bucketEntry.key; + + Object value = bucketEntry.value; + if (valuesTransformer != null) { + currentIterator = valuesTransformer.transformFromValue(value).iterator(); + } else + currentIterator = Collections.singletonList((OIdentifiable) value).iterator(); + + nextEntriesIndex--; + + if (nextEntriesIndex < 0) { + entries = hashTable.lowerEntries(entries[0].key); + + nextEntriesIndex = entries.length - 1; + } + } + + if (currentIterator != null && !currentIterator.hasNext()) + return nextCursorValue(); + + currentIterator = null; + return null; + } + + private Map.Entry nextCursorValue() { + final OIdentifiable identifiable = currentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return currentKey; + } + + @Override + public OIdentifiable getValue() { + return identifiable; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public OIndexKeyCursor keyCursor() { + return new OIndexKeyCursor() { + private int nextEntriesIndex; + private OHashIndexBucket.Entry[] entries; + + { + OHashIndexBucket.Entry firstEntry = hashTable.firstEntry(); + if (firstEntry == null) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else + entries = hashTable.ceilingEntries(firstEntry.key); + } + + @Override + public Object next(int prefetchSize) { + if (entries.length == 0) { + return null; + } + + final OHashIndexBucket.Entry bucketEntry = entries[nextEntriesIndex]; + nextEntriesIndex++; + if (nextEntriesIndex >= entries.length) { + entries = hashTable.higherEntries(entries[entries.length - 1].key); + + nextEntriesIndex = 0; + } + + return bucketEntry.key; + } + }; + } + + @Override + public boolean acquireAtomicExclusiveLock(Object key) { + hashTable.acquireAtomicExclusiveLock(); + return true; + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/engine/ORemoteIndexEngine.java b/core/src/main/java/com/orientechnologies/orient/core/index/engine/ORemoteIndexEngine.java new file mode 100755 index 00000000000..a7cf3835364 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/engine/ORemoteIndexEngine.java @@ -0,0 +1,193 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.engine; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin + * @since 18.07.13 + */ +public class ORemoteIndexEngine implements OIndexEngine { + private final String name; + + public ORemoteIndexEngine(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getIndexNameByKey(Object key) { + return name; + } + + @Override + public void init(String indexName, String indexType, OIndexDefinition indexDefinition, boolean isAutomatic, ODocument metadata) { + } + + @Override + public void flush() { + } + + @Override + public void create(OBinarySerializer valueSerializer, boolean isAutomatic, OType[] keyTypes, boolean nullPointerSupport, + OBinarySerializer keySerializer, int keySize, Set clustersToIndex, Map engineProperties, + ODocument metadata) { + } + + @Override + public void deleteWithoutLoad(String indexName) { + } + + @Override + public void delete() { + } + + @Override + public void load(String indexName, OBinarySerializer valueSerializer, boolean isAutomatic, OBinarySerializer keySerializer, + OType[] keyTypes, boolean nullPointerSupport, int keySize, Map engineProperties) { + } + + @Override + public boolean contains(Object key) { + return false; + } + + @Override + public boolean remove(Object key) { + return false; + } + + @Override + public void clear() { + } + + @Override + public void close() { + } + + @Override + public Object get(Object key) { + return null; + } + + @Override + public void put(Object key, Object value) { + } + + @Override + public boolean validatedPut(Object key, OIdentifiable value, Validator validator) { + return false; + } + + @Override + public Object getFirstKey() { + return null; + } + + @Override + public Object getLastKey() { + return null; + } + + @Override + public OIndexCursor iterateEntriesBetween(Object rangeFrom, boolean fromInclusive, Object rangeTo, boolean toInclusive, + boolean ascSortOrder, ValuesTransformer transformer) { + return new EntriesBetweenCursor(); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean isInclusive, boolean ascSortOrder, + ValuesTransformer transformer) { + return new EntriesMajorCursor(); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean isInclusive, boolean ascSortOrder, ValuesTransformer transformer) { + return new EntriesMinorCursor(); + } + + @Override + public long size(ValuesTransformer transformer) { + return 0; + } + + @Override + public boolean hasRangeQuerySupport() { + return false; + } + + @Override + public OIndexCursor cursor(ValuesTransformer valuesTransformer) { + throw new UnsupportedOperationException("cursor"); + } + + @Override + public OIndexCursor descCursor(ValuesTransformer valuesTransformer) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndexKeyCursor keyCursor() { + throw new UnsupportedOperationException("keyCursor"); + } + + @Override + public int getVersion() { + return -1; + } + + @Override + public boolean acquireAtomicExclusiveLock(Object key) { + throw new UnsupportedOperationException("atomic locking is not supported by remote index engine"); + } + + private static class EntriesBetweenCursor extends OIndexAbstractCursor { + @Override + public Map.Entry nextEntry() { + return null; + } + } + + private static class EntriesMajorCursor extends OIndexAbstractCursor { + @Override + public Map.Entry nextEntry() { + return null; + } + } + + private static class EntriesMinorCursor extends OIndexAbstractCursor { + @Override + public Map.Entry nextEntry() { + return null; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/engine/OSBTreeIndexEngine.java b/core/src/main/java/com/orientechnologies/orient/core/index/engine/OSBTreeIndexEngine.java new file mode 100755 index 00000000000..582c4ecbcb2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/engine/OSBTreeIndexEngine.java @@ -0,0 +1,310 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.engine; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.index.sbtree.local.OSBTree; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin + * @since 8/30/13 + */ +public class OSBTreeIndexEngine implements OIndexEngine { + public static final int VERSION = 1; + + public static final String DATA_FILE_EXTENSION = ".sbt"; + public static final String NULL_BUCKET_FILE_EXTENSION = ".nbt"; + + private final OSBTree sbTree; + private int version; + private final String name; + + public OSBTreeIndexEngine(String name, Boolean durableInNonTxMode, OAbstractPaginatedStorage storage, int version) { + this.name = name; + boolean durableInNonTx; + + if (durableInNonTxMode == null) + durableInNonTx = OGlobalConfiguration.INDEX_DURABLE_IN_NON_TX_MODE.getValueAsBoolean(); + else + durableInNonTx = durableInNonTxMode; + + this.version = version; + + sbTree = new OSBTree(name, DATA_FILE_EXTENSION, durableInNonTx, NULL_BUCKET_FILE_EXTENSION, storage); + } + + @Override + public void init(String indexName, String indexType, OIndexDefinition indexDefinition, boolean isAutomatic, ODocument metadata) { + } + + @Override + public String getName() { + return name; + } + + @Override + public void flush() { + } + + @Override + public void create(OBinarySerializer valueSerializer, boolean isAutomatic, OType[] keyTypes, boolean nullPointerSupport, + OBinarySerializer keySerializer, int keySize, Set clustersToIndex, Map engineProperties, + ODocument metadata) { + sbTree.create(keySerializer, valueSerializer, keyTypes, keySize, nullPointerSupport); + } + + @Override + public void delete() { + sbTree.delete(); + } + + @Override + public void deleteWithoutLoad(String indexName) { + sbTree.deleteWithoutLoad(indexName); + } + + @Override + public void load(String indexName, OBinarySerializer valueSerializer, boolean isAutomatic, OBinarySerializer keySerializer, + OType[] keyTypes, boolean nullPointerSupport, int keySize, Map engineProperties) { + sbTree.load(indexName, keySerializer, valueSerializer, keyTypes, keySize, nullPointerSupport); + } + + @Override + public boolean contains(Object key) { + return sbTree.get(key) != null; + } + + @Override + public boolean remove(Object key) { + return sbTree.remove(key) != null; + } + + @Override + public int getVersion() { + return version; + } + + @Override + public void clear() { + sbTree.clear(); + } + + @Override + public void close() { + sbTree.close(); + } + + @Override + public Object get(Object key) { + return sbTree.get(key); + } + + @Override + public OIndexCursor cursor(ValuesTransformer valuesTransformer) { + final Object firstKey = sbTree.firstKey(); + if (firstKey == null) + return new NullCursor(); + + return new OSBTreeIndexCursor(sbTree.iterateEntriesMajor(firstKey, true, true), valuesTransformer); + } + + @Override + public OIndexCursor descCursor(ValuesTransformer valuesTransformer) { + final Object lastKey = sbTree.lastKey(); + if (lastKey == null) + return new NullCursor(); + + return new OSBTreeIndexCursor(sbTree.iterateEntriesMinor(lastKey, true, false), valuesTransformer); + } + + @Override + public OIndexKeyCursor keyCursor() { + return new OIndexKeyCursor() { + private final OSBTree.OSBTreeKeyCursor sbTreeKeyCursor = sbTree.keyCursor(); + + @Override + public Object next(int prefetchSize) { + return sbTreeKeyCursor.next(prefetchSize); + } + }; + } + + @Override + public void put(Object key, Object value) { + sbTree.put(key, value); + } + + @SuppressWarnings("unchecked") + @Override + public boolean validatedPut(Object key, OIdentifiable value, Validator validator) { + return sbTree.validatedPut(key, value, (Validator) validator); + } + + @Override + public Object getFirstKey() { + return sbTree.firstKey(); + } + + @Override + public Object getLastKey() { + return sbTree.lastKey(); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object rangeFrom, boolean fromInclusive, Object rangeTo, boolean toInclusive, + boolean ascSortOrder, ValuesTransformer transformer) { + return new OSBTreeIndexCursor(sbTree.iterateEntriesBetween(rangeFrom, fromInclusive, rangeTo, toInclusive, ascSortOrder), + transformer); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean isInclusive, boolean ascSortOrder, + ValuesTransformer transformer) { + return new OSBTreeIndexCursor(sbTree.iterateEntriesMajor(fromKey, isInclusive, ascSortOrder), transformer); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean isInclusive, boolean ascSortOrder, ValuesTransformer transformer) { + return new OSBTreeIndexCursor(sbTree.iterateEntriesMinor(toKey, isInclusive, ascSortOrder), transformer); + } + + @Override + public long size(final ValuesTransformer transformer) { + if (transformer == null) + return sbTree.size(); + else { + int counter = 0; + + if (sbTree.isNullPointerSupport()) { + final Object nullValue = sbTree.get(null); + if (nullValue != null) { + counter += transformer.transformFromValue(nullValue).size(); + } + } + + final Object firstKey = sbTree.firstKey(); + final Object lastKey = sbTree.lastKey(); + + if (firstKey != null && lastKey != null) { + final OSBTree.OSBTreeCursor cursor = sbTree.iterateEntriesBetween(firstKey, true, lastKey, true, true); + Map.Entry entry = cursor.next(-1); + while (entry != null) { + counter += transformer.transformFromValue(entry.getValue()).size(); + entry = cursor.next(-1); + } + + return counter; + } + + return counter; + } + } + + @Override + public boolean hasRangeQuerySupport() { + return true; + } + + @Override + public boolean acquireAtomicExclusiveLock(Object key) { + sbTree.acquireAtomicExclusiveLock(); + return true; + } + + @Override + public String getIndexNameByKey(Object key) { + return name; + } + + private static final class OSBTreeIndexCursor extends OIndexAbstractCursor { + private final OSBTree.OSBTreeCursor treeCursor; + private final ValuesTransformer valuesTransformer; + + private Iterator currentIterator = OEmptyIterator.IDENTIFIABLE_INSTANCE; + private Object currentKey = null; + + private OSBTreeIndexCursor(OSBTree.OSBTreeCursor treeCursor, ValuesTransformer valuesTransformer) { + this.treeCursor = treeCursor; + this.valuesTransformer = valuesTransformer; + } + + @Override + public Map.Entry nextEntry() { + if (valuesTransformer == null) { + final Object entry = treeCursor.next(getPrefetchSize()); + return (Map.Entry) entry; + } + + if (currentIterator == null) + return null; + + while (!currentIterator.hasNext()) { + final Object p = treeCursor.next(getPrefetchSize()); + final Map.Entry entry = (Map.Entry) p; + + if (entry == null) { + currentIterator = null; + return null; + } + + currentKey = entry.getKey(); + currentIterator = valuesTransformer.transformFromValue(entry.getValue()).iterator(); + } + + final OIdentifiable value = currentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + return currentKey; + } + + @Override + public OIdentifiable getValue() { + return value; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + } + + private static class NullCursor extends OIndexAbstractCursor { + @Override + public Map.Entry nextEntry() { + return null; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryFirstPage.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryFirstPage.java new file mode 100755 index 00000000000..ccad688cffa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryFirstPage.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 5/14/14 + */ +public class ODirectoryFirstPage extends ODirectoryPage { + private static final int TREE_SIZE_OFFSET = NEXT_FREE_POSITION; + private static final int TOMBSTONE_OFFSET = TREE_SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + + private static final int ITEMS_OFFSET = TOMBSTONE_OFFSET + OIntegerSerializer.INT_SIZE; + + public static final int NODES_PER_PAGE = (OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024 - ITEMS_OFFSET) + / OHashTableDirectory.BINARY_LEVEL_SIZE; + + public ODirectoryFirstPage(OCacheEntry cacheEntry, OWALChanges changes, OCacheEntry entry) { + super(cacheEntry, changes, entry); + } + + public void setTreeSize(int treeSize) throws IOException { + setIntValue(TREE_SIZE_OFFSET, treeSize); + } + + public int getTreeSize() { + return getIntValue(TREE_SIZE_OFFSET); + } + + public void setTombstone(int tombstone) throws IOException { + setIntValue(TOMBSTONE_OFFSET, tombstone); + } + + public int getTombstone() { + return getIntValue(TOMBSTONE_OFFSET); + } + + @Override + protected int getItemsOffset() { + return ITEMS_OFFSET; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryPage.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryPage.java new file mode 100755 index 00000000000..9f773dd554d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ODirectoryPage.java @@ -0,0 +1,99 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 5/14/14 + */ +public class ODirectoryPage extends ODurablePage { + private static final int ITEMS_OFFSET = NEXT_FREE_POSITION; + + public static final int NODES_PER_PAGE = (OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024 - ITEMS_OFFSET) + / OHashTableDirectory.BINARY_LEVEL_SIZE; + + private final OCacheEntry entry; + + public ODirectoryPage(OCacheEntry cacheEntry, OWALChanges changes, OCacheEntry entry) { + super(cacheEntry, changes); + this.entry = entry; + } + + public OCacheEntry getEntry() { + return entry; + } + + public void setMaxLeftChildDepth(int localNodeIndex, byte maxLeftChildDepth) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE; + setByteValue(offset, maxLeftChildDepth); + } + + public byte getMaxLeftChildDepth(int localNodeIndex) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE; + return getByteValue(offset); + } + + public void setMaxRightChildDepth(int localNodeIndex, byte maxRightChildDepth) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + OByteSerializer.BYTE_SIZE; + setByteValue(offset, maxRightChildDepth); + } + + public byte getMaxRightChildDepth(int localNodeIndex) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + OByteSerializer.BYTE_SIZE; + return getByteValue(offset); + } + + public void setNodeLocalDepth(int localNodeIndex, byte nodeLocalDepth) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + 2 * OByteSerializer.BYTE_SIZE; + setByteValue(offset, nodeLocalDepth); + } + + public byte getNodeLocalDepth(int localNodeIndex) { + int offset = getItemsOffset() + localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + 2 * OByteSerializer.BYTE_SIZE; + return getByteValue(offset); + } + + public void setPointer(int localNodeIndex, int index, long pointer) throws IOException { + int offset = getItemsOffset() + (localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + 3 * OByteSerializer.BYTE_SIZE) + + index * OHashTableDirectory.ITEM_SIZE; + + setLongValue(offset, pointer); + } + + public long getPointer(int localNodeIndex, int index) { + int offset = getItemsOffset() + (localNodeIndex * OHashTableDirectory.BINARY_LEVEL_SIZE + 3 * OByteSerializer.BYTE_SIZE) + + index * OHashTableDirectory.ITEM_SIZE; + + return getLongValue(offset); + } + + protected int getItemsOffset() { + return ITEMS_OFFSET; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashFunction.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashFunction.java new file mode 100755 index 00000000000..a89ebbba8b0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashFunction.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +/** + * @author Andrey Lomakin + * @since 12.03.13 + */ +public interface OHashFunction { + public long hashCode(V value); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexBucket.java new file mode 100755 index 00000000000..34f24772559 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexBucket.java @@ -0,0 +1,388 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * @author Andrey Lomakin + * @since 2/17/13 + */ +public class OHashIndexBucket extends ODurablePage implements Iterable> { + private static final int FREE_POINTER_OFFSET = NEXT_FREE_POSITION; + private static final int DEPTH_OFFSET = FREE_POINTER_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int SIZE_OFFSET = DEPTH_OFFSET + OByteSerializer.BYTE_SIZE; + private static final int HISTORY_OFFSET = SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + + private static final int NEXT_REMOVED_BUCKET_OFFSET = HISTORY_OFFSET + OLongSerializer.LONG_SIZE * 64; + private static final int POSITIONS_ARRAY_OFFSET = NEXT_REMOVED_BUCKET_OFFSET + OLongSerializer.LONG_SIZE; + + public static final int MAX_BUCKET_SIZE_BYTES = OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024; + + private final OBinarySerializer keySerializer; + private final OBinarySerializer valueSerializer; + private final OType[] keyTypes; + private final Comparator keyComparator = ODefaultComparator.INSTANCE; + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public OHashIndexBucket(int depth, OCacheEntry cacheEntry, OBinarySerializer keySerializer, + OBinarySerializer valueSerializer, OType[] keyTypes, OWALChanges changes) throws IOException { + super(cacheEntry, changes); + + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + this.keyTypes = keyTypes; + + init(depth); + } + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public OHashIndexBucket(OCacheEntry cacheEntry, OBinarySerializer keySerializer, OBinarySerializer valueSerializer, + OType[] keyTypes, OWALChanges changes) { + super(cacheEntry, changes); + + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + this.keyTypes = keyTypes; + } + + public void init(int depth) throws IOException { + setByteValue(DEPTH_OFFSET, (byte) depth); + setIntValue(FREE_POINTER_OFFSET, MAX_BUCKET_SIZE_BYTES); + setIntValue(SIZE_OFFSET, 0); + } + + public Entry find(final K key, final long hashCode) { + final int index = binarySearch(key, hashCode); + if (index < 0) + return null; + + return getEntry(index); + } + + private int binarySearch(K key, long hashCode) { + int low = 0; + int high = size() - 1; + + while (low <= high) { + final int mid = (low + high) >>> 1; + + final long midHashCode = getHashCode(mid); + final int cmp; + if (lessThanUnsigned(midHashCode, hashCode)) + cmp = -1; + else if (greaterThanUnsigned(midHashCode, hashCode)) + cmp = 1; + else { + final K midVal = getKey(mid); + cmp = keyComparator.compare(midVal, key); + } + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + private static boolean lessThanUnsigned(long longOne, long longTwo) { + return (longOne + Long.MIN_VALUE) < (longTwo + Long.MIN_VALUE); + } + + private static boolean greaterThanUnsigned(long longOne, long longTwo) { + return (longOne + Long.MIN_VALUE) > (longTwo + Long.MIN_VALUE); + } + + public Entry getEntry(int index) { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE); + + final long hashCode = getLongValue(entryPosition); + entryPosition += OLongSerializer.LONG_SIZE; + + final K key = deserializeFromDirectMemory(keySerializer, entryPosition); + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + + final V value = deserializeFromDirectMemory(valueSerializer, entryPosition); + return new Entry(key, value, hashCode); + } + + public long getHashCode(int index) { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE); + return getLongValue(entryPosition); + } + + public K getKey(int index) { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE); + + return deserializeFromDirectMemory(keySerializer, entryPosition + OLongSerializer.LONG_SIZE); + } + + /** + * Obtains the value stored under the given index in this bucket. + * + * @param index the value index. + * + * @return the obtained value. + */ + public V getValue(int index) { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE); + + // skip hash code + entryPosition += OLongSerializer.LONG_SIZE; + + // skip key + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + + return deserializeFromDirectMemory(valueSerializer, entryPosition); + } + + public int getIndex(final long hashCode, final K key) { + return binarySearch(key, hashCode); + } + + public int size() { + return getIntValue(SIZE_OFFSET); + } + + public Iterator> iterator() { + return new EntryIterator(0); + } + + public Iterator> iterator(int index) { + return new EntryIterator(index); + } + + public int mergedSize(OHashIndexBucket buddyBucket) { + return POSITIONS_ARRAY_OFFSET + size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue( + FREE_POINTER_OFFSET)) + buddyBucket.size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue( + FREE_POINTER_OFFSET)); + } + + public int getContentSize() { + return POSITIONS_ARRAY_OFFSET + size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue( + FREE_POINTER_OFFSET)); + } + + public int updateEntry(int index, V value) throws IOException { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE); + entryPosition += OLongSerializer.LONG_SIZE; + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + + final int newSize = valueSerializer.getObjectSize(value); + final int oldSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition); + if (newSize != oldSize) + return -1; + + byte[] newSerializedValue = new byte[newSize]; + valueSerializer.serializeNativeObject(value, newSerializedValue, 0); + + byte[] oldSerializedValue = getBinaryValue(entryPosition, oldSize); + + if (ODefaultComparator.INSTANCE.compare(oldSerializedValue, newSerializedValue) == 0) + return 0; + + setBinaryValue(entryPosition, newSerializedValue); + return 1; + } + + public Entry deleteEntry(int index) throws IOException { + final Entry removedEntry = getEntry(index); + + final int freePointer = getIntValue(FREE_POINTER_OFFSET); + + final int positionOffset = POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE; + final int entryPosition = getIntValue(positionOffset); + + final int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition + OLongSerializer.LONG_SIZE); + final int ridSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition + keySize + OLongSerializer.LONG_SIZE); + final int entrySize = keySize + ridSize + OLongSerializer.LONG_SIZE; + + moveData(positionOffset + OIntegerSerializer.INT_SIZE, positionOffset, + size() * OIntegerSerializer.INT_SIZE - (index + 1) * OIntegerSerializer.INT_SIZE); + + if (entryPosition > freePointer) + moveData(freePointer, freePointer + entrySize, entryPosition - freePointer); + + int currentPositionOffset = POSITIONS_ARRAY_OFFSET; + int size = size(); + for (int i = 0; i < size - 1; i++) { + int currentEntryPosition = getIntValue(currentPositionOffset); + if (currentEntryPosition < entryPosition) + setIntValue(currentPositionOffset, currentEntryPosition + entrySize); + currentPositionOffset += OIntegerSerializer.INT_SIZE; + } + + setIntValue(FREE_POINTER_OFFSET, freePointer + entrySize); + setIntValue(SIZE_OFFSET, size - 1); + + return removedEntry; + } + + public boolean addEntry(long hashCode, K key, V value) throws IOException { + int entreeSize = + keySerializer.getObjectSize(key, (Object[]) keyTypes) + valueSerializer.getObjectSize(value) + OLongSerializer.LONG_SIZE; + int freePointer = getIntValue(FREE_POINTER_OFFSET); + + int size = size(); + if (freePointer - entreeSize < POSITIONS_ARRAY_OFFSET + (size + 1) * OIntegerSerializer.INT_SIZE) + return false; + + final int index = binarySearch(key, hashCode); + if (index >= 0) + throw new IllegalArgumentException("Given value is present in bucket."); + + final int insertionPoint = -index - 1; + insertEntry(hashCode, key, value, insertionPoint, entreeSize); + + return true; + } + + private void insertEntry(long hashCode, K key, V value, int insertionPoint, int entreeSize) throws IOException { + int freePointer = getIntValue(FREE_POINTER_OFFSET); + int size = size(); + + final int positionsOffset = insertionPoint * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET; + + moveData(positionsOffset, positionsOffset + OIntegerSerializer.INT_SIZE, + size() * OIntegerSerializer.INT_SIZE - insertionPoint * OIntegerSerializer.INT_SIZE); + + final int entreePosition = freePointer - entreeSize; + setIntValue(positionsOffset, entreePosition); + serializeEntry(hashCode, key, value, entreePosition); + + setIntValue(FREE_POINTER_OFFSET, entreePosition); + setIntValue(SIZE_OFFSET, size + 1); + } + + public void appendEntry(long hashCode, K key, V value) throws IOException { + final int positionsOffset = size() * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET; + final int entreeSize = + keySerializer.getObjectSize(key, (Object[]) keyTypes) + valueSerializer.getObjectSize(value) + OLongSerializer.LONG_SIZE; + + final int freePointer = getIntValue(FREE_POINTER_OFFSET); + final int entreePosition = freePointer - entreeSize; + + setIntValue(positionsOffset, entreePosition); + serializeEntry(hashCode, key, value, entreePosition); + + setIntValue(FREE_POINTER_OFFSET, freePointer - entreeSize); + setIntValue(SIZE_OFFSET, size() + 1); + } + + private void serializeEntry(long hashCode, K key, V value, int entryOffset) throws IOException { + setLongValue(entryOffset, hashCode); + entryOffset += OLongSerializer.LONG_SIZE; + + final int keySize = keySerializer.getObjectSize(key, (Object[]) keyTypes); + byte[] binaryKey = new byte[keySize]; + keySerializer.serializeNativeObject(key, binaryKey, 0, (Object[]) keyTypes); + setBinaryValue(entryOffset, binaryKey); + + entryOffset += keySize; + + final int valueSize = valueSerializer.getObjectSize(value); + final byte[] binaryValue = new byte[valueSize]; + valueSerializer.serializeNativeObject(value, binaryValue, 0); + + setBinaryValue(entryOffset, binaryValue); + } + + public int getDepth() { + return getByteValue(DEPTH_OFFSET); + } + + public void setDepth(int depth) { + setByteValue(DEPTH_OFFSET, (byte) depth); + } + + public long getNextRemovedBucketPair() { + return getLongValue(NEXT_REMOVED_BUCKET_OFFSET); + } + + public void setNextRemovedBucketPair(long nextRemovedBucketPair) throws IOException { + setLongValue(NEXT_REMOVED_BUCKET_OFFSET, nextRemovedBucketPair); + } + + public long getSplitHistory(int level) { + return getLongValue(HISTORY_OFFSET + OLongSerializer.LONG_SIZE * level); + } + + public void setSplitHistory(int level, long position) throws IOException { + setLongValue(HISTORY_OFFSET + OLongSerializer.LONG_SIZE * level, position); + } + + public static class Entry { + public final K key; + public final V value; + public final long hashCode; + + public Entry(K key, V value, long hashCode) { + this.key = key; + this.value = value; + this.hashCode = hashCode; + } + } + + private final class EntryIterator implements Iterator> { + private int currentIndex; + + private EntryIterator(int currentIndex) { + this.currentIndex = currentIndex; + } + + @Override + public boolean hasNext() { + return currentIndex < size(); + } + + @Override + public Entry next() { + if (currentIndex >= size()) + throw new NoSuchElementException("Iterator was reached last element"); + + final Entry entry = getEntry(currentIndex); + currentIndex++; + return entry; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove operation is not supported"); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFactory.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFactory.java new file mode 100755 index 00000000000..1c669d910d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFactory.java @@ -0,0 +1,140 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.index.ODefaultIndexFactory; +import com.orientechnologies.orient.core.index.OIndexDictionary; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.index.OIndexException; +import com.orientechnologies.orient.core.index.OIndexFactory; +import com.orientechnologies.orient.core.index.OIndexFullText; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.index.OIndexNotUnique; +import com.orientechnologies.orient.core.index.OIndexUnique; +import com.orientechnologies.orient.core.index.engine.OHashTableIndexEngine; +import com.orientechnologies.orient.core.index.engine.ORemoteIndexEngine; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OHashIndexFactory implements OIndexFactory { + + private static final Set TYPES; + public static final String HASH_INDEX_ALGORITHM = "HASH_INDEX"; + private static final Set ALGORITHMS; + + static { + final Set types = new HashSet(); + types.add(OClass.INDEX_TYPE.UNIQUE_HASH_INDEX.toString()); + types.add(OClass.INDEX_TYPE.NOTUNIQUE_HASH_INDEX.toString()); + types.add(OClass.INDEX_TYPE.FULLTEXT_HASH_INDEX.toString()); + types.add(OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.toString()); + TYPES = Collections.unmodifiableSet(types); + } + + static { + final Set algorithms = new HashSet(); + algorithms.add(HASH_INDEX_ALGORITHM); + + ALGORITHMS = Collections.unmodifiableSet(algorithms); + } + + /** + * Index types : + *
        + *
      • UNIQUE
      • + *
      • NOTUNIQUE
      • + *
      • FULLTEXT
      • + *
      • DICTIONARY
      • + *
      + */ + public Set getTypes() { + return TYPES; + } + + public Set getAlgorithms() { + return ALGORITHMS; + } + + public OIndexInternal createIndex(String name, ODatabaseDocumentInternal database, String indexType, String algorithm, + String valueContainerAlgorithm, ODocument metadata, int version) throws OConfigurationException { + + if (version < 0) + version = getLastVersion(); + + if (valueContainerAlgorithm == null) + valueContainerAlgorithm = ODefaultIndexFactory.NONE_VALUE_CONTAINER; + + final OStorage storage = database.getStorage(); + + if (OClass.INDEX_TYPE.UNIQUE_HASH_INDEX.toString().equals(indexType)) + return new OIndexUnique(name, indexType, algorithm, version, (OAbstractPaginatedStorage) storage.getUnderlying(), + valueContainerAlgorithm, metadata); + else if (OClass.INDEX_TYPE.NOTUNIQUE_HASH_INDEX.toString().equals(indexType)) + return new OIndexNotUnique(name, indexType, algorithm, version, (OAbstractPaginatedStorage) storage.getUnderlying(), + valueContainerAlgorithm, metadata); + else if (OClass.INDEX_TYPE.FULLTEXT_HASH_INDEX.toString().equals(indexType)) + return new OIndexFullText(name, indexType, algorithm, version, (OAbstractPaginatedStorage) storage.getUnderlying(), + valueContainerAlgorithm, metadata); + else if (OClass.INDEX_TYPE.DICTIONARY_HASH_INDEX.toString().equals(indexType)) + return new OIndexDictionary(name, indexType, algorithm, version, (OAbstractPaginatedStorage) storage.getUnderlying(), + valueContainerAlgorithm, metadata); + + throw new OConfigurationException("Unsupported type: " + indexType); + } + + @Override + public int getLastVersion() { + return OHashTableIndexEngine.VERSION; + } + + @Override + public OIndexEngine createIndexEngine(final String algoritm, final String name, final Boolean durableInNonTxMode, + final OStorage storage, final int version, final Map engineProperties) { + OIndexEngine indexEngine; + + final String storageType = storage.getType(); + if (storageType.equals("memory") || storageType.equals("plocal")) + indexEngine = new OHashTableIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage, version); + else if (storageType.equals("distributed")) + // DISTRIBUTED CASE: HANDLE IT AS FOR LOCAL + indexEngine = new OHashTableIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage.getUnderlying(), + version); + else if (storageType.equals("remote")) + indexEngine = new ORemoteIndexEngine(name); + else + throw new OIndexException("Unsupported storage type: " + storageType); + + return indexEngine; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFileLevelMetadataPage.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFileLevelMetadataPage.java new file mode 100755 index 00000000000..86c0ea9e24a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashIndexFileLevelMetadataPage.java @@ -0,0 +1,152 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 5/8/14 + */ +public class OHashIndexFileLevelMetadataPage extends ODurablePage { + + private final static int RECORDS_COUNT_OFFSET = NEXT_FREE_POSITION; + private final static int KEY_SERIALIZER_ID_OFFSET = RECORDS_COUNT_OFFSET + OLongSerializer.LONG_SIZE; + private final static int VALUE_SERIALIZER_ID_OFFSET = KEY_SERIALIZER_ID_OFFSET + OByteSerializer.BYTE_SIZE; + private final static int METADATA_ARRAY_OFFSET = VALUE_SERIALIZER_ID_OFFSET + OByteSerializer.BYTE_SIZE; + + private final static int ITEM_SIZE = OByteSerializer.BYTE_SIZE + 3 * OLongSerializer.LONG_SIZE; + + public OHashIndexFileLevelMetadataPage(OCacheEntry cacheEntry, OWALChanges changes, boolean isNewPage) throws IOException { + super(cacheEntry, changes); + + if (isNewPage) { + for (int i = 0; i < OLocalHashTable.HASH_CODE_SIZE; i++) + remove(i); + + setRecordsCount(0); + setKeySerializerId((byte) -1); + setValueSerializerId((byte) -1); + } + } + + public void setRecordsCount(long recordsCount) throws IOException { + setLongValue(RECORDS_COUNT_OFFSET, recordsCount); + } + + public long getRecordsCount() throws IOException { + return getLongValue(RECORDS_COUNT_OFFSET); + } + + public void setKeySerializerId(byte keySerializerId) throws IOException { + setByteValue(KEY_SERIALIZER_ID_OFFSET, keySerializerId); + } + + public byte getKeySerializerId() throws IOException { + return getByteValue(KEY_SERIALIZER_ID_OFFSET); + } + + public void setValueSerializerId(byte valueSerializerId) throws IOException { + setByteValue(VALUE_SERIALIZER_ID_OFFSET, valueSerializerId); + } + + public byte getValueSerializerId() throws IOException { + return getByteValue(VALUE_SERIALIZER_ID_OFFSET); + } + + public void setFileMetadata(int index, long fileId, long bucketsCount, long tombstoneIndex) throws IOException { + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + setByteValue(offset, (byte) 1); + + offset += OByteSerializer.BYTE_SIZE; + + setLongValue(offset, fileId); + offset += OLongSerializer.LONG_SIZE; + + setLongValue(offset, bucketsCount); + offset += OLongSerializer.LONG_SIZE; + + setLongValue(offset, tombstoneIndex); + offset += OLongSerializer.LONG_SIZE; + } + + public void setBucketsCount(int index, long bucketsCount) throws IOException { + assert !isRemoved(index); + + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + offset += OByteSerializer.BYTE_SIZE + OLongSerializer.LONG_SIZE; + setLongValue(offset, bucketsCount); + } + + public long getBucketsCount(int index) throws IOException { + assert !isRemoved(index); + + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + offset += OByteSerializer.BYTE_SIZE + OLongSerializer.LONG_SIZE; + return getLongValue(offset); + } + + public void setTombstoneIndex(int index, long tombstoneIndex) throws IOException { + assert !isRemoved(index); + + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + offset += OByteSerializer.BYTE_SIZE + 2 * OLongSerializer.LONG_SIZE; + setLongValue(offset, tombstoneIndex); + } + + public long getTombstoneIndex(int index) { + assert !isRemoved(index); + + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + offset += OByteSerializer.BYTE_SIZE + 2 * OLongSerializer.LONG_SIZE; + return getLongValue(offset); + } + + public long getFileId(int index) { + assert !isRemoved(index); + + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + + offset += OByteSerializer.BYTE_SIZE; + return getLongValue(offset); + } + + public boolean isRemoved(int index) { + final int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + return getByteValue(offset) == 0; + } + + public void remove(int index) { + int offset = METADATA_ARRAY_OFFSET + index * ITEM_SIZE; + setByteValue(offset, (byte) 0); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTable.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTable.java new file mode 100755 index 00000000000..2351eb62535 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTable.java @@ -0,0 +1,176 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Comparator; + +/** + * Created by lomak_000 on 15.04.2015. + */ +public interface OHashTable { + void create(OBinarySerializer keySerializer, OBinarySerializer valueSerializer, OType[] keyTypes, + boolean nullKeyIsSupported); + + OBinarySerializer getKeySerializer(); + + void setKeySerializer(OBinarySerializer keySerializer); + + OBinarySerializer getValueSerializer(); + + void setValueSerializer(OBinarySerializer valueSerializer); + + V get(K key); + + void put(K key, V value); + + /** + * Puts the given value under the given key into this hash table. Validates the operation using the provided validator. + * + * @param key the key to put the value under. + * @param value the value to put. + * @param validator the operation validator. + * + * @return {@code true} if the validator allowed the put, {@code false} otherwise. + * + * @see OIndexEngine.Validator#validate(Object, Object, Object) + */ + boolean validatedPut(K key, V value, OIndexEngine.Validator validator); + + V remove(K key); + + void clear(); + + OHashIndexBucket.Entry[] higherEntries(K key); + + OHashIndexBucket.Entry[] higherEntries(K key, int limit); + + void load(String name, OType[] keyTypes, boolean nullKeyIsSupported); + + void deleteWithoutLoad(String name, OAbstractPaginatedStorage storageLocal); + + OHashIndexBucket.Entry[] ceilingEntries(K key); + + OHashIndexBucket.Entry firstEntry(); + + OHashIndexBucket.Entry lastEntry(); + + OHashIndexBucket.Entry[] lowerEntries(K key); + + OHashIndexBucket.Entry[] floorEntries(K key); + + long size(); + + void close(); + + void delete(); + + void flush(); + + boolean isNullKeyIsSupported(); + + /** + * Acquires exclusive lock in the active atomic operation running on the current thread for this hash table. + */ + void acquireAtomicExclusiveLock(); + + String getName(); + + public static final class BucketPath { + public final BucketPath parent; + public final int hashMapOffset; + public final int itemIndex; + public final int nodeIndex; + public final int nodeGlobalDepth; + public final int nodeLocalDepth; + + public BucketPath(BucketPath parent, int hashMapOffset, int itemIndex, int nodeIndex, int nodeLocalDepth, int nodeGlobalDepth) { + this.parent = parent; + this.hashMapOffset = hashMapOffset; + this.itemIndex = itemIndex; + this.nodeIndex = nodeIndex; + this.nodeGlobalDepth = nodeGlobalDepth; + this.nodeLocalDepth = nodeLocalDepth; + } + } + + public static final class BucketSplitResult { + public final long updatedBucketPointer; + public final long newBucketPointer; + public final int newDepth; + + public BucketSplitResult(long updatedBucketPointer, long newBucketPointer, int newDepth) { + this.updatedBucketPointer = updatedBucketPointer; + this.newBucketPointer = newBucketPointer; + this.newDepth = newDepth; + } + } + + public static final class NodeSplitResult { + public final long[] newNode; + public final boolean allLeftHashMapsEqual; + public final boolean allRightHashMapsEqual; + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public NodeSplitResult(long[] newNode, boolean allLeftHashMapsEqual, boolean allRightHashMapsEqual) { + this.newNode = newNode; + this.allLeftHashMapsEqual = allLeftHashMapsEqual; + this.allRightHashMapsEqual = allRightHashMapsEqual; + } + } + + @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE") + final class KeyHashCodeComparator implements Comparator { + private final Comparator comparator = ODefaultComparator.INSTANCE; + + private final OHashFunction keyHashFunction; + + public KeyHashCodeComparator(OHashFunction keyHashFunction) { + this.keyHashFunction = keyHashFunction; + } + + @Override + public int compare(K keyOne, K keyTwo) { + final long hashCodeOne = keyHashFunction.hashCode(keyOne); + final long hashCodeTwo = keyHashFunction.hashCode(keyTwo); + + if (greaterThanUnsigned(hashCodeOne, hashCodeTwo)) + return 1; + if (lessThanUnsigned(hashCodeOne, hashCodeTwo)) + return -1; + + return comparator.compare(keyOne, keyTwo); + } + + private static boolean lessThanUnsigned(long longOne, long longTwo) { + return (longOne + Long.MIN_VALUE) < (longTwo + Long.MIN_VALUE); + } + + private static boolean greaterThanUnsigned(long longOne, long longTwo) { + return (longOne + Long.MIN_VALUE) > (longTwo + Long.MIN_VALUE); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTableDirectory.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTableDirectory.java new file mode 100755 index 00000000000..a126de8ee55 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OHashTableDirectory.java @@ -0,0 +1,707 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.exception.OHashTableDirectoryException; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.cache.OCachePointer; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +import java.io.IOException; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 5/14/14 + */ +public class OHashTableDirectory extends ODurableComponent { + public static final int ITEM_SIZE = OLongSerializer.LONG_SIZE; + + public static final int LEVEL_SIZE = OLocalHashTable20.MAX_LEVEL_SIZE; + + public static final int BINARY_LEVEL_SIZE = LEVEL_SIZE * ITEM_SIZE + 3 * OByteSerializer.BYTE_SIZE; + + private long fileId; + + private final long firstEntryIndex; + + private final boolean durableInNonTxMode; + + public OHashTableDirectory(String defaultExtension, String name, String lockName, boolean durableInNonTxMode, + OAbstractPaginatedStorage storage) { + super(storage, name, defaultExtension, lockName); + this.durableInNonTxMode = durableInNonTxMode; + this.firstEntryIndex = 0; + } + + public void create() throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(false); + acquireExclusiveLock(); + try { + + fileId = addFile(atomicOperation, getFullName()); + init(); + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during creation of hash table", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + private void init() throws IOException { + OAtomicOperation atomicOperation = startAtomicOperation(false); + try { + OCacheEntry firstEntry = loadPage(atomicOperation, fileId, firstEntryIndex, true); + + if (firstEntry == null) { + firstEntry = addPage(atomicOperation, fileId); + assert firstEntry.getPageIndex() == 0; + } + + pinPage(atomicOperation, firstEntry); + + firstEntry.acquireExclusiveLock(); + try { + ODirectoryFirstPage firstPage = new ODirectoryFirstPage(firstEntry, getChanges(atomicOperation, firstEntry), firstEntry); + + firstPage.setTreeSize(0); + firstPage.setTombstone(-1); + + } finally { + firstEntry.releaseExclusiveLock(); + releasePage(atomicOperation, firstEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during hash table initialization", this), e); + } + } + + public void open() throws IOException { + startOperation(); + try { + acquireExclusiveLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + fileId = openFile(atomicOperation, getFullName()); + final int filledUpTo = (int) getFilledUpTo(atomicOperation, fileId); + + for (int i = 0; i < filledUpTo; i++) { + final OCacheEntry entry = loadPage(atomicOperation, fileId, i, true); + assert entry != null; + + pinPage(atomicOperation, entry); + releasePage(atomicOperation, entry); + } + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void close() throws IOException { + startOperation(); + try { + acquireExclusiveLock(); + try { + readCache.closeFile(fileId, true, writeCache); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void delete() throws IOException { + startOperation(); + try { + final OAtomicOperation atomicOperation = startAtomicOperation(false); + acquireExclusiveLock(); + try { + deleteFile(atomicOperation, fileId); + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during hash table deletion", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void deleteWithoutOpen() throws IOException { + startOperation(); + try { + final OAtomicOperation atomicOperation = startAtomicOperation(false); + acquireExclusiveLock(); + try { + if (isFileExists(atomicOperation, getFullName())) { + fileId = openFile(atomicOperation, getFullName()); + deleteFile(atomicOperation, fileId); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during deletion of hash table", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public int addNewNode(byte maxLeftChildDepth, byte maxRightChildDepth, byte nodeLocalDepth, long[] newNode) throws IOException { + startOperation(); + try { + int nodeIndex; + + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + OCacheEntry firstEntry = loadPage(atomicOperation, fileId, firstEntryIndex, true); + firstEntry.acquireExclusiveLock(); + try { + ODirectoryFirstPage firstPage = new ODirectoryFirstPage(firstEntry, getChanges(atomicOperation, firstEntry), firstEntry); + + final int tombstone = firstPage.getTombstone(); + + if (tombstone >= 0) + nodeIndex = tombstone; + else { + nodeIndex = firstPage.getTreeSize(); + firstPage.setTreeSize(nodeIndex + 1); + } + + if (nodeIndex < ODirectoryFirstPage.NODES_PER_PAGE) { + final int localNodeIndex = nodeIndex; + + firstPage.setMaxLeftChildDepth(localNodeIndex, maxLeftChildDepth); + firstPage.setMaxRightChildDepth(localNodeIndex, maxRightChildDepth); + firstPage.setNodeLocalDepth(localNodeIndex, nodeLocalDepth); + + if (tombstone >= 0) + firstPage.setTombstone((int) firstPage.getPointer(nodeIndex, 0)); + + for (int i = 0; i < newNode.length; i++) + firstPage.setPointer(localNodeIndex, i, newNode[i]); + + } else { + final int pageIndex = nodeIndex / ODirectoryPage.NODES_PER_PAGE; + final int localLevel = nodeIndex % ODirectoryPage.NODES_PER_PAGE; + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, true); + while (cacheEntry == null || cacheEntry.getPageIndex() < pageIndex) { + if (cacheEntry != null) + releasePage(atomicOperation, cacheEntry); + + cacheEntry = addPage(atomicOperation, fileId); + } + + cacheEntry.acquireExclusiveLock(); + try { + ODirectoryPage page = new ODirectoryPage(cacheEntry, getChanges(atomicOperation, cacheEntry), cacheEntry); + + page.setMaxLeftChildDepth(localLevel, maxLeftChildDepth); + page.setMaxRightChildDepth(localLevel, maxRightChildDepth); + page.setNodeLocalDepth(localLevel, nodeLocalDepth); + + if (tombstone >= 0) + firstPage.setTombstone((int) page.getPointer(localLevel, 0)); + + for (int i = 0; i < newNode.length; i++) + page.setPointer(localLevel, i, newNode[i]); + + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + } finally { + firstEntry.releaseExclusiveLock(); + releasePage(atomicOperation, firstEntry); + } + + endAtomicOperation(false, null); + + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (RuntimeException e) { + endAtomicOperation(true, e); + throw e; + } finally { + releaseExclusiveLock(); + } + + return nodeIndex; + } finally { + completeOperation(); + } + } + + public void deleteNode(int nodeIndex) throws IOException { + startOperation(); + try { + final OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + OCacheEntry firstEntry = loadPage(atomicOperation, fileId, firstEntryIndex, true); + firstEntry.acquireExclusiveLock(); + try { + ODirectoryFirstPage firstPage = new ODirectoryFirstPage(firstEntry, getChanges(atomicOperation, firstEntry), firstEntry); + if (nodeIndex < ODirectoryFirstPage.NODES_PER_PAGE) { + firstPage.setPointer(nodeIndex, 0, firstPage.getTombstone()); + firstPage.setTombstone(nodeIndex); + } else { + final int pageIndex = nodeIndex / ODirectoryPage.NODES_PER_PAGE; + final int localNodeIndex = nodeIndex % ODirectoryPage.NODES_PER_PAGE; + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, true); + cacheEntry.acquireExclusiveLock(); + try { + ODirectoryPage page = new ODirectoryPage(cacheEntry, getChanges(atomicOperation, cacheEntry), cacheEntry); + + page.setPointer(localNodeIndex, 0, firstPage.getTombstone()); + firstPage.setTombstone(nodeIndex); + + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + } + } finally { + firstEntry.releaseExclusiveLock(); + releasePage(atomicOperation, firstEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during node deletion", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public byte getMaxLeftChildDepth(int nodeIndex) throws IOException { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + final ODirectoryPage page = loadPage(nodeIndex, false, atomicOperation); + try { + return page.getMaxLeftChildDepth(getLocalNodeIndex(nodeIndex)); + } finally { + releasePage(page, false, atomicOperation); + } + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + public void setMaxLeftChildDepth(int nodeIndex, byte maxLeftChildDepth) throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + + final ODirectoryPage page = loadPage(nodeIndex, true, atomicOperation); + try { + page.setMaxLeftChildDepth(getLocalNodeIndex(nodeIndex), maxLeftChildDepth); + } finally { + releasePage(page, true, atomicOperation); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during setting of max left child depth", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public byte getMaxRightChildDepth(int nodeIndex) throws IOException { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final ODirectoryPage page = loadPage(nodeIndex, false, atomicOperation); + try { + return page.getMaxRightChildDepth(getLocalNodeIndex(nodeIndex)); + } finally { + releasePage(page, false, atomicOperation); + } + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + public void setMaxRightChildDepth(int nodeIndex, byte maxRightChildDepth) throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + + final ODirectoryPage page = loadPage(nodeIndex, true, atomicOperation); + try { + page.setMaxRightChildDepth(getLocalNodeIndex(nodeIndex), maxRightChildDepth); + } finally { + releasePage(page, true, atomicOperation); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during setting of right max child depth", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public byte getNodeLocalDepth(int nodeIndex) throws IOException { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final ODirectoryPage page = loadPage(nodeIndex, false, atomicOperation); + try { + return page.getNodeLocalDepth(getLocalNodeIndex(nodeIndex)); + } finally { + releasePage(page, false, atomicOperation); + } + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + public void setNodeLocalDepth(int nodeIndex, byte localNodeDepth) throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + final ODirectoryPage page = loadPage(nodeIndex, true, atomicOperation); + try { + page.setNodeLocalDepth(getLocalNodeIndex(nodeIndex), localNodeDepth); + } finally { + releasePage(page, true, atomicOperation); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during setting of local node depth", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public long[] getNode(int nodeIndex) throws IOException { + startOperation(); + try { + final long[] node = new long[LEVEL_SIZE]; + + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final ODirectoryPage page = loadPage(nodeIndex, false, atomicOperation); + try { + final int localNodeIndex = getLocalNodeIndex(nodeIndex); + for (int i = 0; i < LEVEL_SIZE; i++) + node[i] = page.getPointer(localNodeIndex, i); + } finally { + releasePage(page, false, atomicOperation); + } + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + + return node; + } finally { + completeOperation(); + } + } + + public void setNode(int nodeIndex, long[] node) throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + + final ODirectoryPage page = loadPage(nodeIndex, true, atomicOperation); + try { + final int localNodeIndex = getLocalNodeIndex(nodeIndex); + for (int i = 0; i < LEVEL_SIZE; i++) + page.setPointer(localNodeIndex, i, node[i]); + } finally { + releasePage(page, true, atomicOperation); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during setting of node", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public long getNodePointer(int nodeIndex, int index) throws IOException { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final ODirectoryPage page = loadPage(nodeIndex, false, atomicOperation); + try { + return page.getPointer(getLocalNodeIndex(nodeIndex), index); + } finally { + releasePage(page, false, atomicOperation); + } + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + public void setNodePointer(int nodeIndex, int index, long pointer) throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + final ODirectoryPage page = loadPage(nodeIndex, true, atomicOperation); + try { + page.setPointer(getLocalNodeIndex(nodeIndex), index, pointer); + } finally { + releasePage(page, true, atomicOperation); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OHashTableDirectoryException("Error during setting of node pointer", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void clear() throws IOException { + startOperation(); + try { + OAtomicOperation atomicOperation = startAtomicOperation(true); + acquireExclusiveLock(); + try { + truncateFile(atomicOperation, fileId); + + init(); + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException + .wrapException(new OHashTableDirectoryException("Error during removing of hash table directory content", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void flush() throws IOException { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + writeCache.flush(fileId); + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + private ODirectoryPage loadPage(int nodeIndex, boolean exclusiveLock, OAtomicOperation atomicOperation) throws IOException { + if (nodeIndex < ODirectoryFirstPage.NODES_PER_PAGE) { + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, firstEntryIndex, true); + + if (exclusiveLock) + cacheEntry.acquireExclusiveLock(); + else + cacheEntry.acquireSharedLock(); + + return new ODirectoryFirstPage(cacheEntry, getChanges(atomicOperation, cacheEntry), cacheEntry); + } + + final int pageIndex = nodeIndex / ODirectoryPage.NODES_PER_PAGE; + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, true); + + if (exclusiveLock) + cacheEntry.acquireExclusiveLock(); + else + cacheEntry.acquireSharedLock(); + + return new ODirectoryPage(cacheEntry, getChanges(atomicOperation, cacheEntry), cacheEntry); + } + + private void releasePage(ODirectoryPage page, boolean exclusiveLock, OAtomicOperation atomicOperation) { + final OCacheEntry cacheEntry = page.getEntry(); + final OCachePointer cachePointer = cacheEntry.getCachePointer(); + + if (exclusiveLock) + cachePointer.releaseExclusiveLock(); + else + cachePointer.releaseSharedLock(); + + releasePage(atomicOperation, cacheEntry); + } + + private int getLocalNodeIndex(int nodeIndex) { + if (nodeIndex < ODirectoryFirstPage.NODES_PER_PAGE) + return nodeIndex; + + return (nodeIndex - ODirectoryFirstPage.NODES_PER_PAGE) % ODirectoryPage.NODES_PER_PAGE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable.java new file mode 100755 index 00000000000..71617c78241 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable.java @@ -0,0 +1,2078 @@ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OLocalHashTableException; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.exception.OTooBigIndexKeyException; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.index.OIndexException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; +import com.orientechnologies.orient.core.storage.impl.local.statistic.OSessionStoragePerformanceStatistic; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Implementation of hash index which is based on extendible hashing + * algorithm. The directory for extindible hashing is implemented in + * {@link com.orientechnologies.orient.core.index.hashindex.local.OHashTableDirectory} class. Directory is not implemented according + * to classic algorithm because of its big memory consumption in case of non-uniform data distribution instead it is implemented + * according too "Multilevel Extendible Hashing Sven Helmer, Thomas Neumann, Guido Moerkotte April 17, 2002". Which has much less + * memory consumption in case of nonuniform data distribution. + *

      + * Index itself uses so called "multilevel schema" when first level contains 256 buckets, when bucket is split it is put at the + * end of other file which represents second level. So if data which are put has distribution close to uniform (this index was + * designed to be use as rid index for DHT storage) buckets split will be preformed in append only manner to speed up index write + * speed. + *

      + * So hash index bucket itself has following structure: + *

        + *
      1. Bucket depth - 1 byte.
      2. + *
      3. Bucket's size - amount of entities (key, value) in one bucket, 4 bytes
      4. + *
      5. Page indexes of parents of this bucket, page indexes of buckets split of which created current bucket - 64*8 bytes.
      6. + *
      7. Offsets of entities stored in this bucket relatively to it's beginning. It is array of int values of undefined size.
      8. + *
      9. Entities itself
      10. + *
      + *

      + * So if 1-st and 2-nd fields are clear. We should discuss the last ones. + *

      + *

      + * Entities in bucket are sorted by key's hash code so each entity has following storage format in bucket: key's hash code (8 + * bytes), key, value. Because entities are stored in sorted order it means that every time when we insert new entity old ones + * should be moved. + *

      + * There are 2 reasons why it is bad: + *

        + *
      1. It will generate write ahead log of enormous size.
      2. + *
      3. The more amount of memory is affected in operation the less speed we will have. In worst case 60 kb of memory should be + * moved.
      4. + *
      + *

      + * To avoid disadvantages listed above entries ara appended to the end of bucket, but their offsets are stored at the beginning of + * bucket. Offsets are stored in sorted order (ordered by hash code of entity's key) so we need to move only small amount of memory + * to store entities in sorted order. + *

      + * About indexes of parents of current bucket. When item is removed from bucket we check space which is needed to store all entities + * of this bucket, it's buddy bucket (bucket which was also created from parent bucket during split) and if space of single bucket + * is enough to save all entities from both buckets we remove these buckets and put all content in parent bucket. That is why we + * need indexes of parents of current bucket. + *

      + * Also hash index has special file of one page long which contains information about state of each level of buckets in index. This + * information is stored as array index of which equals to file level. All array item has following structure: + *

        + *
      1. Is level removed (in case all buckets are empty or level was not created yet) - 1 byte
      2. + *
      3. File's level id - 8 bytes
      4. + *
      5. Amount of buckets in given level - 8 bytes.
      6. + *
      7. Index of page of first removed bucket (not splitted but removed) - 8 bytes
      8. + *
      + * + * @author Andrey Lomakin + * @since 12.03.13 + */ +public class OLocalHashTable extends ODurableComponent implements OHashTable { + private static final int MAX_KEY_SIZE = OGlobalConfiguration.SBTREE_MAX_KEY_SIZE.getValueAsInteger(); + + private static final long HASH_CODE_MIN_VALUE = 0; + private static final long HASH_CODE_MAX_VALUE = 0xFFFFFFFFFFFFFFFFL; + + private final String metadataConfigurationFileExtension; + private final String treeStateFileExtension; + + public static final int HASH_CODE_SIZE = 64; + public static final int MAX_LEVEL_DEPTH = 8; + public static final int MAX_LEVEL_SIZE = 1 << MAX_LEVEL_DEPTH; + + public static final int LEVEL_MASK = Integer.MAX_VALUE >>> (31 - MAX_LEVEL_DEPTH); + + private final OHashFunction keyHashFunction; + + private OBinarySerializer keySerializer; + private OBinarySerializer valueSerializer; + private OType[] keyTypes; + + private final OHashTable.KeyHashCodeComparator comparator; + + private boolean nullKeyIsSupported; + private long nullBucketFileId = -1; + private final String nullBucketFileExtension; + + private long fileStateId; + private long fileId; + + private long hashStateEntryIndex; + + private OHashTableDirectory directory; + + private final boolean durableInNonTxMode; + + public OLocalHashTable(String name, String metadataConfigurationFileExtension, String treeStateFileExtension, + String bucketFileExtension, String nullBucketFileExtension, OHashFunction keyHashFunction, boolean durableInNonTxMode, + OAbstractPaginatedStorage abstractPaginatedStorage) { + super(abstractPaginatedStorage, name, bucketFileExtension, name + bucketFileExtension); + + this.metadataConfigurationFileExtension = metadataConfigurationFileExtension; + this.treeStateFileExtension = treeStateFileExtension; + this.keyHashFunction = keyHashFunction; + this.nullBucketFileExtension = nullBucketFileExtension; + this.durableInNonTxMode = durableInNonTxMode; + + this.comparator = new OHashTable.KeyHashCodeComparator(this.keyHashFunction); + } + + @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") + @Override + public void create(OBinarySerializer keySerializer, OBinarySerializer valueSerializer, OType[] keyTypes, + boolean nullKeyIsSupported) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table creation"), e); + } + + acquireExclusiveLock(); + try { + try { + + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.nullKeyIsSupported = nullKeyIsSupported; + + this.directory = new OHashTableDirectory(treeStateFileExtension, getName(), getFullName(), durableInNonTxMode, storage); + + fileStateId = addFile(atomicOperation, getName() + metadataConfigurationFileExtension); + + directory.create(); + + final OCacheEntry hashStateEntry = addPage(atomicOperation, fileStateId); + pinPage(atomicOperation, hashStateEntry); + + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), true); + + hashStateEntryIndex = hashStateEntry.getPageIndex(); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + final String fileName = getFullName(); + fileId = addFile(atomicOperation, fileName); + + setKeySerializer(keySerializer); + setValueSerializer(valueSerializer); + + initHashTreeState(atomicOperation); + + if (nullKeyIsSupported) + nullBucketFileId = addFile(atomicOperation, getName() + nullBucketFileExtension); + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OStorageException("Error during local hash table creation"), e); + } + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during local hash table creation"), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + @Override + public OBinarySerializer getKeySerializer() { + acquireSharedLock(); + try { + return keySerializer; + } finally { + releaseSharedLock(); + } + } + + @Override + public void setKeySerializer(OBinarySerializer keySerializer) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash set serializer for index keys"), e); + } + + acquireExclusiveLock(); + try { + this.keySerializer = keySerializer; + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + metadataPage.setKeySerializerId(keySerializer.getId()); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + + throw OException.wrapException(new OIndexException("Cannot set serializer for index keys"), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OStorageException("Cannot set serializer for index keys"), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + private void rollback(Exception e) { + try { + endAtomicOperation(true, e); + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Error during operation roolback"), ioe); + } + } + + @Override + public OBinarySerializer getValueSerializer() { + acquireSharedLock(); + try { + return valueSerializer; + } finally { + releaseSharedLock(); + } + } + + @Override + public void setValueSerializer(OBinarySerializer valueSerializer) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table set serializer for index values"), e); + } + + acquireExclusiveLock(); + try { + this.valueSerializer = valueSerializer; + + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + metadataPage.setValueSerializerId(valueSerializer.getId()); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OIndexException("Cannot set serializer for index values"), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OStorageException("Cannot set serializer for index values"), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public V get(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + checkNullSupport(key); + if (key == null) { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) + return null; + + V result = null; + OCacheEntry cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + cacheEntry.acquireSharedLock(); + try { + ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), valueSerializer, + false); + result = nullBucket.getValue(); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + + return result; + } else { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + + OHashTable.BucketPath bucketPath = getBucket(hashCode); + final long bucketPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + if (bucketPointer == 0) + return null; + + final long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + OHashIndexBucket.Entry entry = bucket.find(key, hashCode); + if (entry == null) + return null; + + return entry.value; + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Exception during index value retrieval"), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + @Override + public boolean isNullKeyIsSupported() { + acquireSharedLock(); + try { + return nullKeyIsSupported; + } finally { + releaseSharedLock(); + } + } + + @Override + public void put(K key, V value) { + put(key, value, null); + } + + @Override + public boolean validatedPut(K key, V value, OIndexEngine.Validator validator) { + return put(key, value, validator); + } + + @Override + public V remove(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryDeletionTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table entry deletion"), e); + } + + acquireExclusiveLock(); + try { + checkNullSupport(key); + + int sizeDiff = 0; + if (key != null) { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + + final OHashTable.BucketPath nodePath = getBucket(hashCode); + final long bucketPointer = directory.getNodePointer(nodePath.nodeIndex, nodePath.itemIndex + nodePath.hashMapOffset); + + final long pageIndex = getPageIndex(bucketPointer); + final V removed; + final boolean found; + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + final int positionIndex = bucket.getIndex(hashCode, key); + found = positionIndex >= 0; + + if (found) { + removed = bucket.deleteEntry(positionIndex).value; + sizeDiff--; + } else + removed = null; + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + if (found) { + if (nodePath.parent != null) { + final int hashMapSize = 1 << nodePath.nodeLocalDepth; + + final boolean allMapsContainSameBucket = checkAllMapsContainSameBucket(directory.getNode(nodePath.nodeIndex), + hashMapSize); + if (allMapsContainSameBucket) + mergeNodeToParent(nodePath); + } + + changeSize(sizeDiff, atomicOperation); + } + + endAtomicOperation(false, null); + return removed; + } else { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + endAtomicOperation(false, null); + return null; + } + + V removed = null; + + OCacheEntry cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + if (cacheEntry == null) + cacheEntry = addPage(atomicOperation, nullBucketFileId); + + cacheEntry.acquireExclusiveLock(); + try { + final ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), + valueSerializer, false); + + removed = nullBucket.getValue(); + if (removed != null) { + nullBucket.removeValue(); + sizeDiff--; + } + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + + endAtomicOperation(false, null); + return removed; + } + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OIndexException("Error during index removal"), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OStorageException("Error during index removal"), e); + } finally { + releaseExclusiveLock(); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryDeletionTimer(); + completeOperation(); + } + } + + private void changeSize(int sizeDiff, OAtomicOperation atomicOperation) throws IOException { + if (sizeDiff != 0) { + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + page.setRecordsCount(page.getRecordsCount() + sizeDiff); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } + } + + @Override + public void clear() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table clear"), e); + } + + acquireExclusiveLock(); + try { + if (nullKeyIsSupported) + truncateFile(atomicOperation, nullBucketFileId); + + initHashTreeState(atomicOperation); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OLocalHashTableException("Error during hash table clear", this), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OLocalHashTableException("Error during hash table clear", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + @Override + public OHashIndexBucket.Entry[] higherEntries(K key) { + return higherEntries(key, -1); + } + + @Override + public OHashIndexBucket.Entry[] higherEntries(K key, int limit) { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + OHashTable.BucketPath bucketPath = getBucket(hashCode); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0 || comparator.compare(bucket.getKey(bucket.size() - 1), key) <= 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + final int index = bucket.getIndex(hashCode, key); + final int startIndex; + if (index >= 0) + startIndex = index + 1; + else + startIndex = -index - 1; + + final int endIndex; + if (limit <= 0) + endIndex = bucket.size(); + else + endIndex = Math.min(bucket.size(), startIndex + limit); + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Exception during data retrieval", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public void load(String name, OType[] keyTypes, boolean nullKeyIsSupported) { + startOperation(); + try { + acquireExclusiveLock(); + try { + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.nullKeyIsSupported = nullKeyIsSupported; + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + fileStateId = openFile(atomicOperation, name + metadataConfigurationFileExtension); + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, 0, true); + hashStateEntryIndex = hashStateEntry.getPageIndex(); + + directory = new OHashTableDirectory(treeStateFileExtension, name, getFullName(), durableInNonTxMode, storage); + directory.open(); + + pinPage(atomicOperation, hashStateEntry); + hashStateEntry.acquireSharedLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + OBinarySerializerFactory serializerFactory = OBinarySerializerFactory + .create(storage.getConfiguration().binaryFormatVersion); + + keySerializer = (OBinarySerializer) serializerFactory.getObjectSerializer(page.getKeySerializerId()); + valueSerializer = (OBinarySerializer) serializerFactory.getObjectSerializer(page.getValueSerializerId()); + + } finally { + hashStateEntry.releaseSharedLock(); + releasePage(atomicOperation, hashStateEntry); + } + + if (nullKeyIsSupported) + nullBucketFileId = openFile(atomicOperation, name + nullBucketFileExtension); + + fileId = openFile(atomicOperation, getFullName()); + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Exception during hash table loading", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + @Override + public void deleteWithoutLoad(String name, OAbstractPaginatedStorage storageLocal) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Error during hash table deletion", this), e); + } + + acquireExclusiveLock(); + try { + + if (isFileExists(atomicOperation, name + metadataConfigurationFileExtension)) { + fileStateId = openFile(atomicOperation, name + metadataConfigurationFileExtension); + deleteFile(atomicOperation, fileStateId); + } + + directory = new OHashTableDirectory(treeStateFileExtension, name, getFullName(), durableInNonTxMode, storage); + directory.deleteWithoutOpen(); + + if (isFileExists(atomicOperation, name + nullBucketFileExtension)) { + final long nullBucketId = openFile(atomicOperation, name + nullBucketFileExtension); + deleteFile(atomicOperation, nullBucketId); + } + + if (isFileExists(atomicOperation, getFullName())) { + final long fileId = openFile(atomicOperation, getFullName()); + deleteFile(atomicOperation, fileId); + } + + endAtomicOperation(false, null); + } catch (IOException ioe) { + rollback(ioe); + throw OException.wrapException(new OLocalHashTableException("Cannot delete hash table with name " + name, this), ioe); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OLocalHashTableException("Cannot delete hash table with name " + name, this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + private OHashIndexBucket.Entry[] convertBucketToEntries(final OHashIndexBucket bucket, int startIndex, int endIndex) { + final OHashIndexBucket.Entry[] entries = new OHashIndexBucket.Entry[endIndex - startIndex]; + final Iterator> iterator = bucket.iterator(startIndex); + + for (int i = 0, k = startIndex; k < endIndex; i++, k++) + entries[i] = iterator.next(); + + return entries; + } + + private OHashTable.BucketPath nextBucketToFind(final OHashTable.BucketPath bucketPath, int bucketDepth) throws IOException { + int offset = bucketPath.nodeGlobalDepth - bucketDepth; + + OHashTable.BucketPath currentNode = bucketPath; + int nodeLocalDepth = directory.getNodeLocalDepth(bucketPath.nodeIndex); + + assert directory.getNodeLocalDepth(bucketPath.nodeIndex) == bucketPath.nodeLocalDepth; + + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentNode = bucketPath.parent; + nodeLocalDepth = currentNode.nodeLocalDepth; + assert directory.getNodeLocalDepth(currentNode.nodeIndex) == currentNode.nodeLocalDepth; + } + } + + final int diff = bucketDepth - (currentNode.nodeGlobalDepth - nodeLocalDepth); + final int interval = (1 << (nodeLocalDepth - diff)); + final int firstStartIndex = currentNode.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + + final OHashTable.BucketPath bucketPathToFind; + final int globalIndex = firstStartIndex + interval + currentNode.hashMapOffset; + if (globalIndex >= MAX_LEVEL_SIZE) + bucketPathToFind = nextLevelUp(currentNode); + else { + final int hashMapSize = 1 << currentNode.nodeLocalDepth; + final int hashMapOffset = globalIndex / hashMapSize * hashMapSize; + + final int startIndex = globalIndex - hashMapOffset; + + bucketPathToFind = new OHashTable.BucketPath(currentNode.parent, hashMapOffset, startIndex, currentNode.nodeIndex, + currentNode.nodeLocalDepth, currentNode.nodeGlobalDepth); + } + + return nextNonEmptyNode(bucketPathToFind); + } + + private OHashTable.BucketPath nextNonEmptyNode(OHashTable.BucketPath bucketPath) throws IOException { + nextBucketLoop: + while (bucketPath != null) { + final long[] node = directory.getNode(bucketPath.nodeIndex); + final int startIndex = bucketPath.itemIndex + bucketPath.hashMapOffset; + final int endIndex = MAX_LEVEL_SIZE; + + for (int i = startIndex; i < endIndex; i++) { + final long position = node[i]; + + if (position > 0) { + final int hashMapSize = 1 << bucketPath.nodeLocalDepth; + final int hashMapOffset = (i / hashMapSize) * hashMapSize; + final int itemIndex = i - hashMapOffset; + + return new OHashTable.BucketPath(bucketPath.parent, hashMapOffset, itemIndex, bucketPath.nodeIndex, + bucketPath.nodeLocalDepth, bucketPath.nodeGlobalDepth); + } + + if (position < 0) { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >> 8); + final int childItemOffset = (int) position & 0xFF; + + final OHashTable.BucketPath parent = new OHashTable.BucketPath(bucketPath.parent, 0, i, bucketPath.nodeIndex, + bucketPath.nodeLocalDepth, bucketPath.nodeGlobalDepth); + + final int childLocalDepth = directory.getNodeLocalDepth(childNodeIndex); + bucketPath = new OHashTable.BucketPath(parent, childItemOffset, 0, childNodeIndex, childLocalDepth, + bucketPath.nodeGlobalDepth + childLocalDepth); + + continue nextBucketLoop; + } + } + + bucketPath = nextLevelUp(bucketPath); + } + + return null; + } + + private OHashTable.BucketPath nextLevelUp(OHashTable.BucketPath bucketPath) throws IOException { + if (bucketPath.parent == null) + return null; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth; + + assert directory.getNodeLocalDepth(bucketPath.nodeIndex) == bucketPath.nodeLocalDepth; + + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + + final OHashTable.BucketPath parent = bucketPath.parent; + + if (parent.itemIndex < MAX_LEVEL_SIZE / 2) { + final int nextParentIndex = (parent.itemIndex / pointersSize + 1) * pointersSize; + return new OHashTable.BucketPath(parent.parent, 0, nextParentIndex, parent.nodeIndex, parent.nodeLocalDepth, + parent.nodeGlobalDepth); + } + + final int nextParentIndex = ((parent.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize + 1) * pointersSize + MAX_LEVEL_SIZE / 2; + if (nextParentIndex < MAX_LEVEL_SIZE) + return new OHashTable.BucketPath(parent.parent, 0, nextParentIndex, parent.nodeIndex, parent.nodeLocalDepth, + parent.nodeGlobalDepth); + + return nextLevelUp(new OHashTable.BucketPath(parent.parent, 0, MAX_LEVEL_SIZE - 1, parent.nodeIndex, parent.nodeLocalDepth, + parent.nodeGlobalDepth)); + } + + @Override + public OHashIndexBucket.Entry[] ceilingEntries(K key) { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + OHashTable.BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + final int index = bucket.getIndex(hashCode, key); + final int startIndex; + if (index >= 0) + startIndex = index; + else + startIndex = -index - 1; + + final int endIndex = bucket.size(); + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Error during data retrieval", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public OHashIndexBucket.Entry firstEntry() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OHashTable.BucketPath bucketPath = getBucket(HASH_CODE_MIN_VALUE); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex); + + long pageIndex = getPageIndex(bucketPointer); + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return null; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + return bucket.getEntry(0); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Exception during data read", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public OHashIndexBucket.Entry lastEntry() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OHashTable.BucketPath bucketPath = getBucket(HASH_CODE_MAX_VALUE); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0) { + final OHashTable.BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return null; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + return bucket.getEntry(bucket.size() - 1); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Exception during data read", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public OHashIndexBucket.Entry[] lowerEntries(K key) { + startOperation(); + try { + + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + OHashTable.BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + long pageIndex = getPageIndex(bucketPointer); + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0 || comparator.compare(bucket.getKey(0), key) >= 0) { + final OHashTable.BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + final int startIndex = 0; + final int index = bucket.getIndex(hashCode, key); + + final int endIndex; + if (index >= 0) + endIndex = index; + else + endIndex = -index - 1; + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Exception during data read", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public OHashIndexBucket.Entry[] floorEntries(K key) { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + OHashTable.BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0) { + final OHashTable.BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + final int startIndex = 0; + final int index = bucket.getIndex(hashCode, key); + + final int endIndex; + if (index >= 0) + endIndex = index + 1; + else + endIndex = -index - 1; + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OLocalHashTableException("Exception during data read", this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + private OHashTable.BucketPath prevBucketToFind(final OHashTable.BucketPath bucketPath, int bucketDepth) throws IOException { + int offset = bucketPath.nodeGlobalDepth - bucketDepth; + + OHashTable.BucketPath currentBucket = bucketPath; + int nodeLocalDepth = bucketPath.nodeLocalDepth; + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentBucket = bucketPath.parent; + nodeLocalDepth = currentBucket.nodeLocalDepth; + } + } + + final int diff = bucketDepth - (currentBucket.nodeGlobalDepth - nodeLocalDepth); + final int firstStartIndex = currentBucket.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + final int globalIndex = firstStartIndex + currentBucket.hashMapOffset - 1; + + final OHashTable.BucketPath bucketPathToFind; + if (globalIndex < 0) + bucketPathToFind = prevLevelUp(bucketPath); + else { + final int hashMapSize = 1 << currentBucket.nodeLocalDepth; + final int hashMapOffset = globalIndex / hashMapSize * hashMapSize; + + final int startIndex = globalIndex - hashMapOffset; + + bucketPathToFind = new OHashTable.BucketPath(currentBucket.parent, hashMapOffset, startIndex, currentBucket.nodeIndex, + currentBucket.nodeLocalDepth, currentBucket.nodeGlobalDepth); + } + + return prevNonEmptyNode(bucketPathToFind); + } + + private OHashTable.BucketPath prevNonEmptyNode(OHashTable.BucketPath nodePath) throws IOException { + prevBucketLoop: + while (nodePath != null) { + final long[] node = directory.getNode(nodePath.nodeIndex); + final int startIndex = 0; + final int endIndex = nodePath.itemIndex + nodePath.hashMapOffset; + + for (int i = endIndex; i >= startIndex; i--) { + final long position = node[i]; + if (position > 0) { + final int hashMapSize = 1 << nodePath.nodeLocalDepth; + final int hashMapOffset = (i / hashMapSize) * hashMapSize; + final int itemIndex = i - hashMapOffset; + + return new OHashTable.BucketPath(nodePath.parent, hashMapOffset, itemIndex, nodePath.nodeIndex, nodePath.nodeLocalDepth, + nodePath.nodeGlobalDepth); + } + + if (position < 0) { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >> 8); + final int childItemOffset = (int) position & 0xFF; + final int nodeLocalDepth = directory.getNodeLocalDepth(childNodeIndex); + final int endChildIndex = (1 << nodeLocalDepth) - 1; + + final OHashTable.BucketPath parent = new OHashTable.BucketPath(nodePath.parent, 0, i, nodePath.nodeIndex, + nodePath.nodeLocalDepth, nodePath.nodeGlobalDepth); + nodePath = new OHashTable.BucketPath(parent, childItemOffset, endChildIndex, childNodeIndex, nodeLocalDepth, + parent.nodeGlobalDepth + nodeLocalDepth); + continue prevBucketLoop; + } + } + + nodePath = prevLevelUp(nodePath); + } + + return null; + } + + private OHashTable.BucketPath prevLevelUp(OHashTable.BucketPath bucketPath) { + if (bucketPath.parent == null) + return null; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth; + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + + final OHashTable.BucketPath parent = bucketPath.parent; + + if (parent.itemIndex > MAX_LEVEL_SIZE / 2) { + final int prevParentIndex = ((parent.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize) * pointersSize + MAX_LEVEL_SIZE / 2 - 1; + return new OHashTable.BucketPath(parent.parent, 0, prevParentIndex, parent.nodeIndex, parent.nodeLocalDepth, + parent.nodeGlobalDepth); + } + + final int prevParentIndex = (parent.itemIndex / pointersSize) * pointersSize - 1; + if (prevParentIndex >= 0) + return new OHashTable.BucketPath(parent.parent, 0, prevParentIndex, parent.nodeIndex, parent.nodeLocalDepth, + parent.nodeGlobalDepth); + + return prevLevelUp(new OHashTable.BucketPath(parent.parent, 0, 0, parent.nodeIndex, parent.nodeLocalDepth, -1)); + } + + @Override + public long size() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireSharedLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + return metadataPage.getRecordsCount(); + } finally { + hashStateEntry.releaseSharedLock(); + releasePage(atomicOperation, hashStateEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Error during index size request", this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public void close() { + startOperation(); + try { + acquireExclusiveLock(); + try { + flush(); + + directory.close(); + readCache.closeFile(fileStateId, true, writeCache); + readCache.closeFile(fileId, true, writeCache); + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Error during hash table close", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + @Override + public void delete() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Error during hash table deletion", this), e); + } + + acquireExclusiveLock(); + try { + directory.delete(); + deleteFile(atomicOperation, fileStateId); + deleteFile(atomicOperation, fileId); + + if (nullKeyIsSupported) + deleteFile(atomicOperation, nullBucketFileId); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OLocalHashTableException("Exception during index deletion", this), e); + } catch (Exception e) { + rollback(e); + + throw OException.wrapException(new OLocalHashTableException("Exception during index deletion", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + private void mergeNodeToParent(OHashTable.BucketPath nodePath) throws IOException { + final int startIndex = findParentNodeStartIndex(nodePath); + final int localNodeDepth = nodePath.nodeLocalDepth; + final int hashMapSize = 1 << localNodeDepth; + + final int parentIndex = nodePath.parent.nodeIndex; + for (int i = 0, k = startIndex; i < MAX_LEVEL_SIZE; i += hashMapSize, k++) { + directory.setNodePointer(parentIndex, k, directory.getNodePointer(nodePath.nodeIndex, i)); + } + + directory.deleteNode(nodePath.nodeIndex); + + if (nodePath.parent.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxChildDepth = directory.getMaxLeftChildDepth(parentIndex); + if (maxChildDepth == localNodeDepth) + directory.setMaxLeftChildDepth(parentIndex, (byte) getMaxLevelDepth(parentIndex, 0, MAX_LEVEL_SIZE / 2)); + } else { + final int maxChildDepth = directory.getMaxRightChildDepth(parentIndex); + if (maxChildDepth == localNodeDepth) + directory.setMaxRightChildDepth(parentIndex, (byte) getMaxLevelDepth(parentIndex, MAX_LEVEL_SIZE / 2, MAX_LEVEL_SIZE)); + } + } + + public void flush() { + startOperation(); + try { + acquireExclusiveLock(); + try { + writeCache.flush(fileStateId); + writeCache.flush(fileId); + + directory.flush(); + + if (nullKeyIsSupported) + writeCache.flush(nullBucketFileId); + } catch (IOException e) { + throw OException.wrapException(new OLocalHashTableException("Error during hash table flush", this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + @Override + public void acquireAtomicExclusiveLock() { + atomicOperationsManager.acquireExclusiveLockTillOperationComplete(this); + } + + private boolean put(K key, V value, OIndexEngine.Validator validator) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryUpdateTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table entry put"), e); + } + acquireExclusiveLock(); + try { + + checkNullSupport(key); + + if (key != null) { + final int keySize = keySerializer.getObjectSize(key, (Object[]) keyTypes); + if (keySize > MAX_KEY_SIZE) + throw new OTooBigIndexKeyException( + "Key size is more than allowed, operation was canceled. Current key size " + keySize + ", allowed " + MAX_KEY_SIZE, + getName()); + } + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final boolean putResult = doPut(key, value, validator, atomicOperation); + endAtomicOperation(false, null); + return putResult; + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OIndexException("Error during index update"), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OStorageException("Error during index update"), e); + } finally { + releaseExclusiveLock(); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryUpdateTimer(); + completeOperation(); + } + } + + @SuppressWarnings("unchecked") + private boolean doPut(K key, V value, OIndexEngine.Validator validator, OAtomicOperation atomicOperation) + throws IOException { + int sizeDiff = 0; + + if (key == null) { + boolean isNew; + OCacheEntry cacheEntry; + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + cacheEntry = addPage(atomicOperation, nullBucketFileId); + isNew = true; + } else { + cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + isNew = false; + } + + cacheEntry.acquireExclusiveLock(); + try { + ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), valueSerializer, isNew); + + final V oldValue = nullBucket.getValue(); + + if (validator != null) { + final Object result = validator.validate(null, oldValue, value); + if (result == OIndexEngine.Validator.IGNORE) + return false; + + value = (V) result; + } + + if (oldValue != null) + sizeDiff--; + + nullBucket.setValue(value); + sizeDiff++; + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + return true; + } else { + final long hashCode = keyHashFunction.hashCode(key); + + final OHashTable.BucketPath bucketPath = getBucket(hashCode); + final long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + if (bucketPointer == 0) + throw new IllegalStateException("In this version of hash table buckets are added through split only."); + + final long pageIndex = getPageIndex(bucketPointer); + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + final int index = bucket.getIndex(hashCode, key); + + if (validator != null) { + final V oldValue = index > -1 ? bucket.getValue(index) : null; + final Object result = validator.validate(key, oldValue, value); + if (result == OIndexEngine.Validator.IGNORE) + return false; + + value = (V) result; + } + + if (index > -1) { + final int updateResult = bucket.updateEntry(index, value); + if (updateResult == 0) { + changeSize(sizeDiff, atomicOperation); + return true; + } + + if (updateResult == 1) { + changeSize(sizeDiff, atomicOperation); + return true; + } + + assert updateResult == -1; + + bucket.deleteEntry(index); + sizeDiff--; + } + + if (bucket.addEntry(hashCode, key, value)) { + sizeDiff++; + + changeSize(sizeDiff, atomicOperation); + return true; + } + + final OHashTable.BucketSplitResult splitResult = splitBucket(bucket, pageIndex, atomicOperation); + + final long updatedBucketPointer = splitResult.updatedBucketPointer; + final long newBucketPointer = splitResult.newBucketPointer; + final int bucketDepth = splitResult.newDepth; + + if (bucketDepth <= bucketPath.nodeGlobalDepth) { + updateNodeAfterBucketSplit(bucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } else { + if (bucketPath.nodeLocalDepth < MAX_LEVEL_DEPTH) { + final OHashTable.NodeSplitResult nodeSplitResult = splitNode(bucketPath); + + assert !(nodeSplitResult.allLeftHashMapsEqual && nodeSplitResult.allRightHashMapsEqual); + + final long[] newNode = nodeSplitResult.newNode; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth + 1; + final int hashMapSize = 1 << nodeLocalDepth; + + assert nodeSplitResult.allRightHashMapsEqual == checkAllMapsContainSameBucket(newNode, hashMapSize); + + int newNodeIndex = -1; + if (!nodeSplitResult.allRightHashMapsEqual || bucketPath.itemIndex >= MAX_LEVEL_SIZE / 2) + newNodeIndex = directory.addNewNode((byte) 0, (byte) 0, (byte) nodeLocalDepth, newNode); + + final int updatedItemIndex = bucketPath.itemIndex << 1; + final int updatedOffset = bucketPath.hashMapOffset << 1; + final int updatedGlobalDepth = bucketPath.nodeGlobalDepth + 1; + + boolean allLeftHashMapsEqual = nodeSplitResult.allLeftHashMapsEqual; + boolean allRightHashMapsEqual = nodeSplitResult.allRightHashMapsEqual; + + if (updatedOffset < MAX_LEVEL_SIZE) { + allLeftHashMapsEqual = false; + final OHashTable.BucketPath updatedBucketPath = new OHashTable.BucketPath(bucketPath.parent, updatedOffset, + updatedItemIndex, bucketPath.nodeIndex, nodeLocalDepth, updatedGlobalDepth); + updateNodeAfterBucketSplit(updatedBucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } else { + allRightHashMapsEqual = false; + final OHashTable.BucketPath newBucketPath = new OHashTable.BucketPath(bucketPath.parent, + updatedOffset - MAX_LEVEL_SIZE, updatedItemIndex, newNodeIndex, nodeLocalDepth, updatedGlobalDepth); + updateNodeAfterBucketSplit(newBucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } + + updateNodesAfterSplit(bucketPath, bucketPath.nodeIndex, newNode, nodeLocalDepth, hashMapSize, allLeftHashMapsEqual, + allRightHashMapsEqual, newNodeIndex); + + if (allLeftHashMapsEqual) + directory.deleteNode(bucketPath.nodeIndex); + } else { + addNewLevelNode(bucketPath, bucketPath.nodeIndex, newBucketPointer, updatedBucketPointer); + } + } + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + doPut(key, value, null /* already validated */, atomicOperation); + return true; + } + } + + private void checkNullSupport(K key) { + if (key == null && !nullKeyIsSupported) + throw new OLocalHashTableException("Null keys are not supported.", this); + } + + private void updateNodesAfterSplit(OHashTable.BucketPath bucketPath, int nodeIndex, long[] newNode, int nodeLocalDepth, + int hashMapSize, boolean allLeftHashMapEquals, boolean allRightHashMapsEquals, int newNodeIndex) throws IOException { + + final int startIndex = findParentNodeStartIndex(bucketPath); + + final int parentNodeIndex = bucketPath.parent.nodeIndex; + assert assertParentNodeStartIndex(bucketPath, directory.getNode(parentNodeIndex), startIndex); + + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + if (allLeftHashMapEquals) { + for (int i = 0; i < pointersSize; i++) { + final long position = directory.getNodePointer(nodeIndex, i * hashMapSize); + directory.setNodePointer(parentNodeIndex, startIndex + i, position); + } + } else { + for (int i = 0; i < pointersSize; i++) + directory.setNodePointer(parentNodeIndex, startIndex + i, (bucketPath.nodeIndex << 8) | (i * hashMapSize) | Long.MIN_VALUE); + } + + if (allRightHashMapsEquals) { + for (int i = 0; i < pointersSize; i++) { + final long position = newNode[i * hashMapSize]; + directory.setNodePointer(parentNodeIndex, startIndex + pointersSize + i, position); + } + } else { + for (int i = 0; i < pointersSize; i++) + directory.setNodePointer(parentNodeIndex, startIndex + pointersSize + i, + (newNodeIndex << 8) | (i * hashMapSize) | Long.MIN_VALUE); + } + + updateMaxChildDepth(bucketPath.parent, bucketPath.nodeLocalDepth + 1); + } + + private void updateMaxChildDepth(OHashTable.BucketPath parentPath, int childDepth) throws IOException { + if (parentPath == null) + return; + + if (parentPath.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxChildDepth = directory.getMaxLeftChildDepth(parentPath.nodeIndex); + if (childDepth > maxChildDepth) + directory.setMaxLeftChildDepth(parentPath.nodeIndex, (byte) childDepth); + } else { + final int maxChildDepth = directory.getMaxRightChildDepth(parentPath.nodeIndex); + if (childDepth > maxChildDepth) + directory.setMaxRightChildDepth(parentPath.nodeIndex, (byte) childDepth); + } + } + + private boolean assertParentNodeStartIndex(OHashTable.BucketPath bucketPath, long[] parentNode, int calculatedIndex) { + int startIndex = -1; + for (int i = 0; i < parentNode.length; i++) + if (parentNode[i] < 0 && (parentNode[i] & Long.MAX_VALUE) >>> 8 == bucketPath.nodeIndex) { + startIndex = i; + break; + } + + return startIndex == calculatedIndex; + } + + private int findParentNodeStartIndex(OHashTable.BucketPath bucketPath) { + final OHashTable.BucketPath parentBucketPath = bucketPath.parent; + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - bucketPath.nodeLocalDepth); + + if (parentBucketPath.itemIndex < MAX_LEVEL_SIZE / 2) + return (parentBucketPath.itemIndex / pointersSize) * pointersSize; + + return ((parentBucketPath.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize) * pointersSize + MAX_LEVEL_SIZE / 2; + } + + private void addNewLevelNode(OHashTable.BucketPath bucketPath, int nodeIndex, long newBucketPointer, long updatedBucketPointer) + throws IOException { + final int newNodeDepth; + final int newNodeStartIndex; + final int mapInterval; + + if (bucketPath.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxDepth = directory.getMaxLeftChildDepth(bucketPath.nodeIndex); + + assert getMaxLevelDepth(bucketPath.nodeIndex, 0, MAX_LEVEL_SIZE / 2) == maxDepth; + + if (maxDepth > 0) + newNodeDepth = maxDepth; + else + newNodeDepth = 1; + + mapInterval = 1 << (MAX_LEVEL_DEPTH - newNodeDepth); + newNodeStartIndex = (bucketPath.itemIndex / mapInterval) * mapInterval; + } else { + final int maxDepth = directory.getMaxRightChildDepth(bucketPath.nodeIndex); + assert getMaxLevelDepth(bucketPath.nodeIndex, MAX_LEVEL_SIZE / 2, MAX_LEVEL_SIZE) == maxDepth; + if (maxDepth > 0) + newNodeDepth = maxDepth; + else + newNodeDepth = 1; + + mapInterval = 1 << (MAX_LEVEL_DEPTH - newNodeDepth); + newNodeStartIndex = ((bucketPath.itemIndex - MAX_LEVEL_SIZE / 2) / mapInterval) * mapInterval + MAX_LEVEL_SIZE / 2; + } + + final int newNodeIndex = directory.addNewNode((byte) 0, (byte) 0, (byte) newNodeDepth, new long[MAX_LEVEL_SIZE]); + + final int mapSize = 1 << newNodeDepth; + for (int i = 0; i < mapInterval; i++) { + final int nodeOffset = i + newNodeStartIndex; + final long bucketPointer = directory.getNodePointer(nodeIndex, nodeOffset); + + if (nodeOffset != bucketPath.itemIndex) { + for (int n = i << newNodeDepth; n < (i + 1) << newNodeDepth; n++) + directory.setNodePointer(newNodeIndex, n, bucketPointer); + } else { + for (int n = i << newNodeDepth; n < (2 * i + 1) << (newNodeDepth - 1); n++) + directory.setNodePointer(newNodeIndex, n, updatedBucketPointer); + + for (int n = (2 * i + 1) << (newNodeDepth - 1); n < (i + 1) << newNodeDepth; n++) + directory.setNodePointer(newNodeIndex, n, newBucketPointer); + } + + directory.setNodePointer(nodeIndex, nodeOffset, (newNodeIndex << 8) | (i * mapSize) | Long.MIN_VALUE); + } + + updateMaxChildDepth(bucketPath, newNodeDepth); + } + + private int getMaxLevelDepth(int nodeIndex, int start, int end) throws IOException { + int currentIndex = -1; + int maxDepth = 0; + + for (int i = start; i < end; i++) { + final long nodePosition = directory.getNodePointer(nodeIndex, i); + if (nodePosition >= 0) + continue; + + final int index = (int) ((nodePosition & Long.MAX_VALUE) >>> 8); + if (index == currentIndex) + continue; + + currentIndex = index; + + final int nodeLocalDepth = directory.getNodeLocalDepth(index); + if (maxDepth < nodeLocalDepth) + maxDepth = nodeLocalDepth; + } + + return maxDepth; + } + + private void updateNodeAfterBucketSplit(OHashTable.BucketPath bucketPath, int bucketDepth, long newBucketPointer, + long updatedBucketPointer) throws IOException { + int offset = bucketPath.nodeGlobalDepth - (bucketDepth - 1); + OHashTable.BucketPath currentNode = bucketPath; + int nodeLocalDepth = bucketPath.nodeLocalDepth; + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentNode = bucketPath.parent; + nodeLocalDepth = currentNode.nodeLocalDepth; + } + } + + final int diff = bucketDepth - 1 - (currentNode.nodeGlobalDepth - nodeLocalDepth); + + final int interval = (1 << (nodeLocalDepth - diff - 1)); + final int firstStartIndex = currentNode.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + final int firstEndIndex = firstStartIndex + interval; + + final int secondStartIndex = firstEndIndex; + final int secondEndIndex = secondStartIndex + interval; + + for (int i = firstStartIndex; i < firstEndIndex; i++) + updateBucket(currentNode.nodeIndex, i, currentNode.hashMapOffset, updatedBucketPointer); + + for (int i = secondStartIndex; i < secondEndIndex; i++) + updateBucket(currentNode.nodeIndex, i, currentNode.hashMapOffset, newBucketPointer); + } + + private boolean checkAllMapsContainSameBucket(long[] newNode, int hashMapSize) { + int n = 0; + boolean allHashMapsEquals = true; + while (n < newNode.length) { + boolean allHashBucketEquals = true; + for (int i = 0; i < hashMapSize - 1; i++) { + if (newNode[i + n] != newNode[i + n + 1]) { + allHashBucketEquals = false; + break; + } + } + n += hashMapSize; + if (!allHashBucketEquals) { + allHashMapsEquals = false; + break; + } + } + + assert assertAllNodesAreFilePointers(allHashMapsEquals, newNode, hashMapSize); + + return allHashMapsEquals; + } + + private boolean assertAllNodesAreFilePointers(boolean allHashMapsEquals, long[] newNode, int hashMapSize) { + if (allHashMapsEquals) { + int n = 0; + while (n < newNode.length) { + for (int i = 0; i < hashMapSize; i++) { + if (newNode[i] < 0) { + return false; + } + } + n += hashMapSize; + } + } + + return true; + } + + private OHashTable.NodeSplitResult splitNode(OHashTable.BucketPath bucketPath) throws IOException { + final long[] newNode = new long[MAX_LEVEL_SIZE]; + final int hashMapSize = 1 << (bucketPath.nodeLocalDepth + 1); + + boolean hashMapItemsAreEqual = true; + boolean allLeftItemsAreEqual; + boolean allRightItemsAreEqual; + + int mapCounter = 0; + long firstPosition = -1; + + long[] node = directory.getNode(bucketPath.nodeIndex); + + for (int i = MAX_LEVEL_SIZE / 2; i < MAX_LEVEL_SIZE; i++) { + final long position = node[i]; + if (hashMapItemsAreEqual && mapCounter == 0) + firstPosition = position; + + newNode[2 * (i - MAX_LEVEL_SIZE / 2)] = position; + newNode[2 * (i - MAX_LEVEL_SIZE / 2) + 1] = position; + + if (hashMapItemsAreEqual) { + hashMapItemsAreEqual = firstPosition == position; + mapCounter += 2; + + if (mapCounter >= hashMapSize) + mapCounter = 0; + } + } + + mapCounter = 0; + allRightItemsAreEqual = hashMapItemsAreEqual; + + hashMapItemsAreEqual = true; + final long[] updatedNode = new long[node.length]; + for (int i = 0; i < MAX_LEVEL_SIZE / 2; i++) { + final long position = node[i]; + if (hashMapItemsAreEqual && mapCounter == 0) + firstPosition = position; + + updatedNode[2 * i] = position; + updatedNode[2 * i + 1] = position; + + if (hashMapItemsAreEqual) { + hashMapItemsAreEqual = firstPosition == position; + + mapCounter += 2; + + if (mapCounter >= hashMapSize) + mapCounter = 0; + } + } + + allLeftItemsAreEqual = hashMapItemsAreEqual; + + directory.setNode(bucketPath.nodeIndex, updatedNode); + directory.setNodeLocalDepth(bucketPath.nodeIndex, (byte) (directory.getNodeLocalDepth(bucketPath.nodeIndex) + 1)); + + return new OHashTable.NodeSplitResult(newNode, allLeftItemsAreEqual, allRightItemsAreEqual); + } + + private void splitBucketContent(OHashIndexBucket bucket, OHashIndexBucket newBucket, int newBucketDepth) + throws IOException { + assert checkBucketDepth(bucket); + + List> entries = new ArrayList>(bucket.size()); + for (OHashIndexBucket.Entry entry : bucket) { + entries.add(entry); + } + + bucket.init(newBucketDepth); + + for (OHashIndexBucket.Entry entry : entries) { + if (((keyHashFunction.hashCode(entry.key) >>> (HASH_CODE_SIZE - newBucketDepth)) & 1) == 0) + bucket.appendEntry(entry.hashCode, entry.key, entry.value); + else + newBucket.appendEntry(entry.hashCode, entry.key, entry.value); + } + + assert checkBucketDepth(bucket); + assert checkBucketDepth(newBucket); + } + + private OHashTable.BucketSplitResult splitBucket(OHashIndexBucket bucket, long pageIndex, OAtomicOperation atomicOperation) + throws IOException { + int bucketDepth = bucket.getDepth(); + int newBucketDepth = bucketDepth + 1; + + final long updatedBucketIndex = pageIndex; + final OCacheEntry newBucketCacheEntry = addPage(atomicOperation, fileId); + + newBucketCacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket newBucket = new OHashIndexBucket(newBucketDepth, newBucketCacheEntry, keySerializer, + valueSerializer, keyTypes, getChanges(atomicOperation, newBucketCacheEntry)); + + splitBucketContent(bucket, newBucket, newBucketDepth); + + final long updatedBucketPointer = createBucketPointer(updatedBucketIndex); + final long newBucketPointer = createBucketPointer(newBucketCacheEntry.getPageIndex()); + + return new OHashTable.BucketSplitResult(updatedBucketPointer, newBucketPointer, newBucketDepth); + } finally { + newBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, newBucketCacheEntry); + } + } + + private boolean checkBucketDepth(OHashIndexBucket bucket) { + int bucketDepth = bucket.getDepth(); + + if (bucket.size() == 0) + return true; + + final Iterator> positionIterator = bucket.iterator(); + + long firstValue = keyHashFunction.hashCode(positionIterator.next().key) >>> (HASH_CODE_SIZE - bucketDepth); + while (positionIterator.hasNext()) { + final long value = keyHashFunction.hashCode(positionIterator.next().key) >>> (HASH_CODE_SIZE - bucketDepth); + if (value != firstValue) + return false; + } + + return true; + } + + private void updateBucket(int nodeIndex, int itemIndex, int offset, long newBucketPointer) throws IOException { + final long position = directory.getNodePointer(nodeIndex, itemIndex + offset); + if (position >= 0) + directory.setNodePointer(nodeIndex, itemIndex + offset, newBucketPointer); + else { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >>> 8); + final int childOffset = (int) (position & 0xFF); + final int childNodeDepth = directory.getNodeLocalDepth(childNodeIndex); + final int interval = 1 << childNodeDepth; + for (int i = 0; i < interval; i++) { + updateBucket(childNodeIndex, i, childOffset, newBucketPointer); + } + } + } + + @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") + private void initHashTreeState(OAtomicOperation atomicOperation) throws IOException { + truncateFile(atomicOperation, fileId); + + for (long pageIndex = 0; pageIndex < MAX_LEVEL_SIZE; pageIndex++) { + final OCacheEntry cacheEntry = addPage(atomicOperation, fileId); + assert cacheEntry.getPageIndex() == pageIndex; + + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket emptyBucket = new OHashIndexBucket(MAX_LEVEL_DEPTH, cacheEntry, keySerializer, + valueSerializer, keyTypes, getChanges(atomicOperation, cacheEntry)); + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + final long[] rootTree = new long[MAX_LEVEL_SIZE]; + for (int pageIndex = 0; pageIndex < MAX_LEVEL_SIZE; pageIndex++) + rootTree[pageIndex] = createBucketPointer(pageIndex); + + directory.clear(); + directory.addNewNode((byte) 0, (byte) 0, (byte) MAX_LEVEL_DEPTH, rootTree); + + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + metadataPage.setRecordsCount(0); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } + + private long createBucketPointer(long pageIndex) { + return pageIndex + 1; + } + + private long getPageIndex(long bucketPointer) { + return bucketPointer - 1; + } + + private OHashTable.BucketPath getBucket(final long hashCode) throws IOException { + int localNodeDepth = directory.getNodeLocalDepth(0); + int nodeDepth = localNodeDepth; + OHashTable.BucketPath parentNode = null; + int nodeIndex = 0; + int offset = 0; + + int index = (int) ((hashCode >>> (HASH_CODE_SIZE - nodeDepth)) & (LEVEL_MASK >>> (MAX_LEVEL_DEPTH - localNodeDepth))); + OHashTable.BucketPath currentNode = new OHashTable.BucketPath(null, 0, index, 0, localNodeDepth, nodeDepth); + do { + final long position = directory.getNodePointer(nodeIndex, index + offset); + if (position >= 0) + return currentNode; + + nodeIndex = (int) ((position & Long.MAX_VALUE) >>> 8); + offset = (int) (position & 0xFF); + + localNodeDepth = directory.getNodeLocalDepth(nodeIndex); + nodeDepth += localNodeDepth; + + index = (int) ((hashCode >>> (HASH_CODE_SIZE - nodeDepth)) & (LEVEL_MASK >>> (MAX_LEVEL_DEPTH - localNodeDepth))); + + parentNode = currentNode; + currentNode = new OHashTable.BucketPath(parentNode, offset, index, nodeIndex, localNodeDepth, nodeDepth); + } while (nodeDepth <= HASH_CODE_SIZE); + + throw new IllegalStateException("Extendible hashing tree in corrupted state."); + } + + @Override + protected void startOperation() { + OSessionStoragePerformanceStatistic sessionStoragePerformanceStatistic = performanceStatisticManager + .getSessionPerformanceStatistic(); + if (sessionStoragePerformanceStatistic != null) { + sessionStoragePerformanceStatistic + .startComponentOperation(getFullName(), OSessionStoragePerformanceStatistic.ComponentType.INDEX); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable20.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable20.java new file mode 100755 index 00000000000..f6430f2b927 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OLocalHashTable20.java @@ -0,0 +1,2232 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.index.OIndexEngineException; +import com.orientechnologies.orient.core.index.OIndexException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; +import com.orientechnologies.orient.core.storage.impl.local.statistic.OSessionStoragePerformanceStatistic; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; + +/** + * Implementation of hash index which is based on extendible hashing + * algorithm. The directory for extindible hashing is implemented in + * {@link com.orientechnologies.orient.core.index.hashindex.local.OHashTableDirectory} class. Directory is not implemented according + * to classic algorithm because of its big memory consumption in case of non-uniform data distribution instead it is implemented + * according too "Multilevel Extendible Hashing Sven Helmer, Thomas Neumann, Guido Moerkotte April 17, 2002". Which has much less + * memory consumption in case of nonuniform data distribution. + *

      + * Index itself uses so called "muiltilevel schema" when first level contains 256 buckets, when bucket is split it is put at the + * end of other file which represents second level. So if data which are put has distribution close to uniform (this index was + * designed to be use as rid index for DHT storage) buckets split will be preformed in append only manner to speed up index write + * speed. + *

      + * So hash index bucket itself has following structure: + *

        + *
      1. Bucket depth - 1 byte.
      2. + *
      3. Bucket's size - amount of entities (key, value) in one bucket, 4 bytes
      4. + *
      5. Page indexes of parents of this bucket, page indexes of buckets split of which created current bucket - 64*8 bytes.
      6. + *
      7. Offsets of entities stored in this bucket relatively to it's beginning. It is array of int values of undefined size.
      8. + *
      9. Entities itself
      10. + *
      + *

      + * So if 1-st and 2-nd fields are clear. We should discuss the last ones. + *

      + *

      + * Entities in bucket are sorted by key's hash code so each entity has following storage format in bucket: key's hash code (8 + * bytes), key, value. Because entities are stored in sorted order it means that every time when we insert new entity old ones + * should be moved. + *

      + * There are 2 reasons why it is bad: + *

        + *
      1. It will generate write ahead log of enormous size.
      2. + *
      3. The more amount of memory is affected in operation the less speed we will have. In worst case 60 kb of memory should be + * moved.
      4. + *
      + *

      + * To avoid disadvantages listed above entries ara appended to the end of bucket, but their offsets are stored at the beginning of + * bucket. Offsets are stored in sorted order (ordered by hash code of entity's key) so we need to move only small amount of memory + * to store entities in sorted order. + *

      + * About indexes of parents of current bucket. When item is removed from bucket we check space which is needed to store all entities + * of this bucket, it's buddy bucket (bucket which was also created from parent bucket during split) and if space of single bucket + * is enough to save all entities from both buckets we remove these buckets and put all content in parent bucket. That is why we + * need indexes of parents of current bucket. + *

      + * Also hash index has special file of one page long which contains information about state of each level of buckets in index. This + * information is stored as array index of which equals to file level. All array item has following structure: + *

        + *
      1. Is level removed (in case all buckets are empty or level was not created yet) - 1 byte
      2. + *
      3. File's level id - 8 bytes
      4. + *
      5. Amount of buckets in given level - 8 bytes.
      6. + *
      7. Index of page of first removed bucket (not splitted but removed) - 8 bytes
      8. + *
      + * + * @author Andrey Lomakin + * @since 12.03.13 + */ +public class OLocalHashTable20 extends ODurableComponent implements OHashTable { + private static final double MERGE_THRESHOLD = 0.2; + + private static final long HASH_CODE_MIN_VALUE = 0; + private static final long HASH_CODE_MAX_VALUE = 0xFFFFFFFFFFFFFFFFL; + + private final String metadataConfigurationFileExtension; + private final String treeStateFileExtension; + + public static final int HASH_CODE_SIZE = 64; + public static final int MAX_LEVEL_DEPTH = 8; + public static final int MAX_LEVEL_SIZE = 1 << MAX_LEVEL_DEPTH; + + public static final int LEVEL_MASK = Integer.MAX_VALUE >>> (31 - MAX_LEVEL_DEPTH); + + private final OHashFunction keyHashFunction; + + private OBinarySerializer keySerializer; + private OBinarySerializer valueSerializer; + private OType[] keyTypes; + + private final KeyHashCodeComparator comparator; + + private boolean nullKeyIsSupported; + private long nullBucketFileId = -1; + private final String nullBucketFileExtension; + + private long fileStateId; + + private long hashStateEntryIndex; + + private OHashTableDirectory directory; + + private final boolean durableInNonTxMode; + + public OLocalHashTable20(String name, String metadataConfigurationFileExtension, String treeStateFileExtension, + String bucketFileExtension, String nullBucketFileExtension, OHashFunction keyHashFunction, boolean durableInNonTxMode, + OAbstractPaginatedStorage abstractPaginatedStorage) { + super(abstractPaginatedStorage, name, bucketFileExtension, name + bucketFileExtension); + + this.metadataConfigurationFileExtension = metadataConfigurationFileExtension; + this.treeStateFileExtension = treeStateFileExtension; + this.keyHashFunction = keyHashFunction; + this.nullBucketFileExtension = nullBucketFileExtension; + this.durableInNonTxMode = durableInNonTxMode; + + this.comparator = new KeyHashCodeComparator(this.keyHashFunction); + } + + @Override + public void create(OBinarySerializer keySerializer, OBinarySerializer valueSerializer, OType[] keyTypes, + boolean nullKeyIsSupported) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table creation"), e); + } + + acquireExclusiveLock(); + try { + try { + + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.nullKeyIsSupported = nullKeyIsSupported; + + this.directory = new OHashTableDirectory(treeStateFileExtension, getName(), getFullName(), durableInNonTxMode, storage); + + fileStateId = addFile(atomicOperation, getName() + metadataConfigurationFileExtension); + + directory.create(); + + final OCacheEntry hashStateEntry = addPage(atomicOperation, fileStateId); + pinPage(atomicOperation, hashStateEntry); + + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), true); + + createFileMetadata(0, page, atomicOperation); + hashStateEntryIndex = hashStateEntry.getPageIndex(); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + setKeySerializer(keySerializer); + setValueSerializer(valueSerializer); + + initHashTreeState(atomicOperation); + + if (nullKeyIsSupported) + nullBucketFileId = addFile(atomicOperation, getName() + nullBucketFileExtension); + + endAtomicOperation(false, null); + } catch (IOException e) { + endAtomicOperation(true, e); + throw e; + } catch (Exception e) { + endAtomicOperation(true, e); + throw OException.wrapException(new OStorageException("Error during local hash table creation"), e); + } + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during local hash table creation"), e); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public OBinarySerializer getKeySerializer() { + acquireSharedLock(); + try { + return keySerializer; + } finally { + releaseSharedLock(); + } + } + + @Override + public void setKeySerializer(OBinarySerializer keySerializer) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash set serializer for index keys"), e); + } + + acquireExclusiveLock(); + try { + this.keySerializer = keySerializer; + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + metadataPage.setKeySerializerId(keySerializer.getId()); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(); + + throw OException.wrapException(new OIndexException("Cannot set serializer for index keys"), e); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OStorageException("Cannot set serializer for index keys"), e); + } finally { + releaseExclusiveLock(); + } + } + + private void rollback() { + try { + endAtomicOperation(true, null); + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Error during operation roolback"), ioe); + } + } + + @Override + public OBinarySerializer getValueSerializer() { + acquireSharedLock(); + try { + return valueSerializer; + } finally { + releaseSharedLock(); + } + } + + @Override + public void setValueSerializer(OBinarySerializer valueSerializer) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table set serializer for index values"), e); + } + + acquireExclusiveLock(); + try { + this.valueSerializer = valueSerializer; + + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + metadataPage.setValueSerializerId(valueSerializer.getId()); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(); + throw OException.wrapException(new OIndexException("Cannot set serializer for index values"), e); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OStorageException("Cannot set serializer for index values"), e); + } finally { + releaseExclusiveLock(); + } + } + + private void createFileMetadata(int fileLevel, OHashIndexFileLevelMetadataPage page, OAtomicOperation atomicOperation) + throws IOException { + final String fileName = getName() + fileLevel + getExtension(); + final long fileId = addFile(atomicOperation, fileName); + + page.setFileMetadata(fileLevel, fileId, 0, -1); + } + + @Override + public V get(K key) { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + checkNullSupport(key); + if (key == null) { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) + return null; + + V result = null; + OCacheEntry cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + try { + ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), valueSerializer, + false); + result = nullBucket.getValue(); + } finally { + releasePage(atomicOperation, cacheEntry); + } + + return result; + } else { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + + BucketPath bucketPath = getBucket(hashCode); + final long bucketPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + if (bucketPointer == 0) + return null; + + long pageIndex = getPageIndex(bucketPointer); + int fileLevel = getFileLevel(bucketPointer); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + OHashIndexBucket.Entry entry = bucket.find(key, hashCode); + if (entry == null) + return null; + + return entry.value; + } finally { + releasePage(atomicOperation, cacheEntry); + } + } + + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Exception during index value retrieval"), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public boolean isNullKeyIsSupported() { + acquireSharedLock(); + try { + return nullKeyIsSupported; + } finally { + releaseSharedLock(); + } + } + + @Override + public void put(K key, V value) { + put(key, value, null); + } + + @Override + public boolean validatedPut(K key, V value, OIndexEngine.Validator validator) { + return put(key, value, validator); + } + + @Override + public V remove(K key) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table entry deletion"), e); + } + + acquireExclusiveLock(); + try { + checkNullSupport(key); + + int sizeDiff = 0; + if (key != null) { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + + final BucketPath nodePath = getBucket(hashCode); + final long bucketPointer = directory.getNodePointer(nodePath.nodeIndex, nodePath.itemIndex + nodePath.hashMapOffset); + + final long pageIndex = getPageIndex(bucketPointer); + final int fileLevel = getFileLevel(bucketPointer); + final V removed; + final boolean found; + + final OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + final int positionIndex = bucket.getIndex(hashCode, key); + found = positionIndex >= 0; + + if (found) { + removed = bucket.deleteEntry(positionIndex).value; + sizeDiff--; + + mergeBucketsAfterDeletion(nodePath, bucket, atomicOperation); + } else + removed = null; + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + if (found) { + if (nodePath.parent != null) { + final int hashMapSize = 1 << nodePath.nodeLocalDepth; + + final boolean allMapsContainSameBucket = checkAllMapsContainSameBucket(directory.getNode(nodePath.nodeIndex), + hashMapSize); + if (allMapsContainSameBucket) + mergeNodeToParent(nodePath); + } + + changeSize(sizeDiff, atomicOperation); + } + + endAtomicOperation(false, null); + return removed; + } else { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + endAtomicOperation(false, null); + return null; + } + + V removed = null; + + OCacheEntry cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + if (cacheEntry == null) + cacheEntry = addPage(atomicOperation, nullBucketFileId); + + cacheEntry.acquireExclusiveLock(); + try { + final ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), valueSerializer, + false); + + removed = nullBucket.getValue(); + if (removed != null) { + nullBucket.removeValue(); + sizeDiff--; + } + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + + endAtomicOperation(false, null); + return removed; + } + } catch (IOException e) { + rollback(); + throw OException.wrapException(new OIndexException("Error during index removal"), e); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OStorageException("Error during index removal"), e); + } finally { + releaseExclusiveLock(); + } + } + + private void changeSize(int sizeDiff, OAtomicOperation atomicOperation) throws IOException { + if (sizeDiff != 0) { + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + page.setRecordsCount(page.getRecordsCount() + sizeDiff); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } + } + + @Override + public void clear() { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table clear"), e); + } + + acquireExclusiveLock(); + try { + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + for (int i = 0; i < HASH_CODE_SIZE; i++) { + if (!page.isRemoved(i)) { + truncateFile(atomicOperation, page.getFileId(i)); + page.setBucketsCount(i, 0); + page.setTombstoneIndex(i, -1); + } + } + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + + if (nullKeyIsSupported) + truncateFile(atomicOperation, nullBucketFileId); + + initHashTreeState(atomicOperation); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(); + throw OException.wrapException(new OIndexEngineException("Error during hash table clear", getName()), e); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OIndexEngineException("Error during hash table clear", getName()), e); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public OHashIndexBucket.Entry[] higherEntries(K key) { + return higherEntries(key, -1); + } + + @Override + public OHashIndexBucket.Entry[] higherEntries(K key, int limit) { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + BucketPath bucketPath = getBucket(hashCode); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0 || comparator.compare(bucket.getKey(bucket.size() - 1), key) <= 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + releasePage(atomicOperation, cacheEntry); + + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + fileLevel = getFileLevel(nextPointer); + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + final int index = bucket.getIndex(hashCode, key); + final int startIndex; + if (index >= 0) + startIndex = index + 1; + else + startIndex = -index - 1; + + final int endIndex; + if (limit <= 0) + endIndex = bucket.size(); + else + endIndex = Math.min(bucket.size(), startIndex + limit); + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + releasePage(atomicOperation, cacheEntry); + } + + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Exception during data retrieval"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public void load(String name, OType[] keyTypes, boolean nullKeyIsSupported) { + acquireExclusiveLock(); + try { + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.nullKeyIsSupported = nullKeyIsSupported; + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + fileStateId = openFile(atomicOperation, name + metadataConfigurationFileExtension); + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, 0, true); + hashStateEntryIndex = hashStateEntry.getPageIndex(); + + directory = new OHashTableDirectory(treeStateFileExtension, name, getFullName(), durableInNonTxMode, storage); + directory.open(); + + pinPage(atomicOperation, hashStateEntry); + try { + OHashIndexFileLevelMetadataPage page = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + keySerializer = (OBinarySerializer) storage.getComponentsFactory().binarySerializerFactory + .getObjectSerializer(page.getKeySerializerId()); + valueSerializer = (OBinarySerializer) storage.getComponentsFactory().binarySerializerFactory + .getObjectSerializer(page.getValueSerializerId()); + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + if (nullKeyIsSupported) + nullBucketFileId = openFile(atomicOperation, name + nullBucketFileExtension); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Exception during hash table loading"), e); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public void deleteWithoutLoad(String name, OAbstractPaginatedStorage storageLocal) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table deletion"), e); + } + + acquireExclusiveLock(); + try { + if (isFileExists(atomicOperation, name + metadataConfigurationFileExtension)) { + fileStateId = openFile(atomicOperation, name + metadataConfigurationFileExtension); + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, 0, true); + + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + for (int i = 0; i < HASH_CODE_SIZE; i++) { + if (!metadataPage.isRemoved(i)) { + final long fileId = metadataPage.getFileId(i); + deleteFile(atomicOperation, fileId); + } + } + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + if (isFileExists(atomicOperation, fileStateId)) + deleteFile(atomicOperation, fileStateId); + + directory = new OHashTableDirectory(treeStateFileExtension, name, getFullName(), durableInNonTxMode, storage); + directory.deleteWithoutOpen(); + + if (isFileExists(atomicOperation, name + nullBucketFileExtension)) { + final long nullBucketId = openFile(atomicOperation, name + nullBucketFileExtension); + deleteFile(atomicOperation, nullBucketId); + } + } + + endAtomicOperation(false, null); + } catch (IOException ioe) { + rollback(); + throw OException.wrapException(new OIndexException("Cannot delete hash table with name " + name), ioe); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OIndexException("Cannot delete hash table with name " + name), e); + } finally { + releaseExclusiveLock(); + } + } + + private OHashIndexBucket.Entry[] convertBucketToEntries(final OHashIndexBucket bucket, int startIndex, int endIndex) { + final OHashIndexBucket.Entry[] entries = new OHashIndexBucket.Entry[endIndex - startIndex]; + final Iterator> iterator = bucket.iterator(startIndex); + + for (int i = 0, k = startIndex; k < endIndex; i++, k++) + entries[i] = iterator.next(); + + return entries; + } + + private BucketPath nextBucketToFind(final BucketPath bucketPath, int bucketDepth) throws IOException { + int offset = bucketPath.nodeGlobalDepth - bucketDepth; + + BucketPath currentNode = bucketPath; + int nodeLocalDepth = directory.getNodeLocalDepth(bucketPath.nodeIndex); + + assert directory.getNodeLocalDepth(bucketPath.nodeIndex) == bucketPath.nodeLocalDepth; + + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentNode = bucketPath.parent; + nodeLocalDepth = currentNode.nodeLocalDepth; + assert directory.getNodeLocalDepth(currentNode.nodeIndex) == currentNode.nodeLocalDepth; + } + } + + final int diff = bucketDepth - (currentNode.nodeGlobalDepth - nodeLocalDepth); + final int interval = (1 << (nodeLocalDepth - diff)); + final int firstStartIndex = currentNode.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + + final BucketPath bucketPathToFind; + final int globalIndex = firstStartIndex + interval + currentNode.hashMapOffset; + if (globalIndex >= MAX_LEVEL_SIZE) + bucketPathToFind = nextLevelUp(currentNode); + else { + final int hashMapSize = 1 << currentNode.nodeLocalDepth; + final int hashMapOffset = globalIndex / hashMapSize * hashMapSize; + + final int startIndex = globalIndex - hashMapOffset; + + bucketPathToFind = new BucketPath(currentNode.parent, hashMapOffset, startIndex, currentNode.nodeIndex, + currentNode.nodeLocalDepth, currentNode.nodeGlobalDepth); + } + + return nextNonEmptyNode(bucketPathToFind); + } + + private BucketPath nextNonEmptyNode(BucketPath bucketPath) throws IOException { + nextBucketLoop: + while (bucketPath != null) { + final long[] node = directory.getNode(bucketPath.nodeIndex); + final int startIndex = bucketPath.itemIndex + bucketPath.hashMapOffset; + final int endIndex = MAX_LEVEL_SIZE; + + for (int i = startIndex; i < endIndex; i++) { + final long position = node[i]; + + if (position > 0) { + final int hashMapSize = 1 << bucketPath.nodeLocalDepth; + final int hashMapOffset = (i / hashMapSize) * hashMapSize; + final int itemIndex = i - hashMapOffset; + + return new BucketPath(bucketPath.parent, hashMapOffset, itemIndex, bucketPath.nodeIndex, bucketPath.nodeLocalDepth, + bucketPath.nodeGlobalDepth); + } + + if (position < 0) { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >> 8); + final int childItemOffset = (int) position & 0xFF; + + final BucketPath parent = new BucketPath(bucketPath.parent, 0, i, bucketPath.nodeIndex, bucketPath.nodeLocalDepth, + bucketPath.nodeGlobalDepth); + + final int childLocalDepth = directory.getNodeLocalDepth(childNodeIndex); + bucketPath = new BucketPath(parent, childItemOffset, 0, childNodeIndex, childLocalDepth, + bucketPath.nodeGlobalDepth + childLocalDepth); + + continue nextBucketLoop; + } + } + + bucketPath = nextLevelUp(bucketPath); + } + + return null; + } + + private BucketPath nextLevelUp(BucketPath bucketPath) throws IOException { + if (bucketPath.parent == null) + return null; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth; + + assert directory.getNodeLocalDepth(bucketPath.nodeIndex) == bucketPath.nodeLocalDepth; + + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + + final BucketPath parent = bucketPath.parent; + + if (parent.itemIndex < MAX_LEVEL_SIZE / 2) { + final int nextParentIndex = (parent.itemIndex / pointersSize + 1) * pointersSize; + return new BucketPath(parent.parent, 0, nextParentIndex, parent.nodeIndex, parent.nodeLocalDepth, parent.nodeGlobalDepth); + } + + final int nextParentIndex = ((parent.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize + 1) * pointersSize + MAX_LEVEL_SIZE / 2; + if (nextParentIndex < MAX_LEVEL_SIZE) + return new BucketPath(parent.parent, 0, nextParentIndex, parent.nodeIndex, parent.nodeLocalDepth, parent.nodeGlobalDepth); + + return nextLevelUp( + new BucketPath(parent.parent, 0, MAX_LEVEL_SIZE - 1, parent.nodeIndex, parent.nodeLocalDepth, parent.nodeGlobalDepth)); + } + + @Override + public OHashIndexBucket.Entry[] ceilingEntries(K key) { + + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + releasePage(atomicOperation, cacheEntry); + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + fileLevel = getFileLevel(nextPointer); + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + final int index = bucket.getIndex(hashCode, key); + final int startIndex; + if (index >= 0) + startIndex = index; + else + startIndex = -index - 1; + + final int endIndex = bucket.size(); + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Error during data retrieval"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public OHashIndexBucket.Entry firstEntry() { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + BucketPath bucketPath = getBucket(HASH_CODE_MIN_VALUE); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0) { + bucketPath = nextBucketToFind(bucketPath, bucket.getDepth()); + if (bucketPath == null) + return null; + + releasePage(atomicOperation, cacheEntry); + final long nextPointer = directory + .getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + fileLevel = getFileLevel(nextPointer); + pageIndex = getPageIndex(nextPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + } + + return bucket.getEntry(0); + } finally { + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Exception during data read"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public OHashIndexBucket.Entry lastEntry() { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + BucketPath bucketPath = getBucket(HASH_CODE_MAX_VALUE); + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + while (bucket.size() == 0) { + final BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return null; + + releasePage(atomicOperation, cacheEntry); + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + fileLevel = getFileLevel(prevPointer); + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + return bucket.getEntry(bucket.size() - 1); + } finally { + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Exception during data read"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public OHashIndexBucket.Entry[] lowerEntries(K key) { + + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0 || comparator.compare(bucket.getKey(0), key) >= 0) { + final BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + releasePage(atomicOperation, cacheEntry); + + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + fileLevel = getFileLevel(prevPointer); + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + final int startIndex = 0; + final int index = bucket.getIndex(hashCode, key); + + final int endIndex; + if (index >= 0) + endIndex = index; + else + endIndex = -index - 1; + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Exception during data read"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public OHashIndexBucket.Entry[] floorEntries(K key) { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final long hashCode = keyHashFunction.hashCode(key); + BucketPath bucketPath = getBucket(hashCode); + + long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + + int fileLevel = getFileLevel(bucketPointer); + long pageIndex = getPageIndex(bucketPointer); + + OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + try { + OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + while (bucket.size() == 0) { + final BucketPath prevBucketPath = prevBucketToFind(bucketPath, bucket.getDepth()); + if (prevBucketPath == null) + return OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + + releasePage(atomicOperation, cacheEntry); + + final long prevPointer = directory + .getNodePointer(prevBucketPath.nodeIndex, prevBucketPath.itemIndex + prevBucketPath.hashMapOffset); + + fileLevel = getFileLevel(prevPointer); + pageIndex = getPageIndex(prevPointer); + + cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + + bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + + bucketPath = prevBucketPath; + } + + final int startIndex = 0; + final int index = bucket.getIndex(hashCode, key); + + final int endIndex; + if (index >= 0) + endIndex = index + 1; + else + endIndex = -index - 1; + + return convertBucketToEntries(bucket, startIndex, endIndex); + } finally { + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OIndexException("Exception during data read"), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + private BucketPath prevBucketToFind(final BucketPath bucketPath, int bucketDepth) throws IOException { + int offset = bucketPath.nodeGlobalDepth - bucketDepth; + + BucketPath currentBucket = bucketPath; + int nodeLocalDepth = bucketPath.nodeLocalDepth; + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentBucket = bucketPath.parent; + nodeLocalDepth = currentBucket.nodeLocalDepth; + } + } + + final int diff = bucketDepth - (currentBucket.nodeGlobalDepth - nodeLocalDepth); + final int firstStartIndex = currentBucket.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + final int globalIndex = firstStartIndex + currentBucket.hashMapOffset - 1; + + final BucketPath bucketPathToFind; + if (globalIndex < 0) + bucketPathToFind = prevLevelUp(bucketPath); + else { + final int hashMapSize = 1 << currentBucket.nodeLocalDepth; + final int hashMapOffset = globalIndex / hashMapSize * hashMapSize; + + final int startIndex = globalIndex - hashMapOffset; + + bucketPathToFind = new BucketPath(currentBucket.parent, hashMapOffset, startIndex, currentBucket.nodeIndex, + currentBucket.nodeLocalDepth, currentBucket.nodeGlobalDepth); + } + + return prevNonEmptyNode(bucketPathToFind); + } + + private BucketPath prevNonEmptyNode(BucketPath nodePath) throws IOException { + prevBucketLoop: + while (nodePath != null) { + final long[] node = directory.getNode(nodePath.nodeIndex); + final int startIndex = 0; + final int endIndex = nodePath.itemIndex + nodePath.hashMapOffset; + + for (int i = endIndex; i >= startIndex; i--) { + final long position = node[i]; + if (position > 0) { + final int hashMapSize = 1 << nodePath.nodeLocalDepth; + final int hashMapOffset = (i / hashMapSize) * hashMapSize; + final int itemIndex = i - hashMapOffset; + + return new BucketPath(nodePath.parent, hashMapOffset, itemIndex, nodePath.nodeIndex, nodePath.nodeLocalDepth, + nodePath.nodeGlobalDepth); + } + + if (position < 0) { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >> 8); + final int childItemOffset = (int) position & 0xFF; + final int nodeLocalDepth = directory.getNodeLocalDepth(childNodeIndex); + final int endChildIndex = (1 << nodeLocalDepth) - 1; + + final BucketPath parent = new BucketPath(nodePath.parent, 0, i, nodePath.nodeIndex, nodePath.nodeLocalDepth, + nodePath.nodeGlobalDepth); + nodePath = new BucketPath(parent, childItemOffset, endChildIndex, childNodeIndex, nodeLocalDepth, + parent.nodeGlobalDepth + nodeLocalDepth); + continue prevBucketLoop; + } + } + + nodePath = prevLevelUp(nodePath); + } + + return null; + } + + private BucketPath prevLevelUp(BucketPath bucketPath) { + if (bucketPath.parent == null) + return null; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth; + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + + final BucketPath parent = bucketPath.parent; + + if (parent.itemIndex > MAX_LEVEL_SIZE / 2) { + final int prevParentIndex = ((parent.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize) * pointersSize + MAX_LEVEL_SIZE / 2 - 1; + return new BucketPath(parent.parent, 0, prevParentIndex, parent.nodeIndex, parent.nodeLocalDepth, parent.nodeGlobalDepth); + } + + final int prevParentIndex = (parent.itemIndex / pointersSize) * pointersSize - 1; + if (prevParentIndex >= 0) + return new BucketPath(parent.parent, 0, prevParentIndex, parent.nodeIndex, parent.nodeLocalDepth, parent.nodeGlobalDepth); + + return prevLevelUp(new BucketPath(parent.parent, 0, 0, parent.nodeIndex, parent.nodeLocalDepth, -1)); + } + + @Override + public long size() { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + return metadataPage.getRecordsCount(); + } finally { + releasePage(atomicOperation, hashStateEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during index size request"), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public void close() { + acquireExclusiveLock(); + try { + flush(); + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + directory.close(); + + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + try { + for (int i = 0; i < HASH_CODE_SIZE; i++) { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + if (!metadataPage.isRemoved(i)) { + readCache.closeFile(metadataPage.getFileId(i), true, writeCache); + } + } + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + readCache.closeFile(fileStateId, true, writeCache); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table close"), e); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public void delete() { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table deletion"), e); + } + + acquireExclusiveLock(); + try { + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + try { + for (int i = 0; i < HASH_CODE_SIZE; i++) { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + if (!metadataPage.isRemoved(i)) { + deleteFile(atomicOperation, metadataPage.getFileId(i)); + } + } + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + directory.delete(); + deleteFile(atomicOperation, fileStateId); + + if (nullKeyIsSupported) + deleteFile(atomicOperation, nullBucketFileId); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(); + + throw OException.wrapException(new OIndexException("Exception during index deletion"), e); + } catch (Exception e) { + rollback(); + + throw OException.wrapException(new OIndexException("Exception during index deletion"), e); + } finally { + releaseExclusiveLock(); + } + } + + private void mergeNodeToParent(BucketPath nodePath) throws IOException { + final int startIndex = findParentNodeStartIndex(nodePath); + final int localNodeDepth = nodePath.nodeLocalDepth; + final int hashMapSize = 1 << localNodeDepth; + + final int parentIndex = nodePath.parent.nodeIndex; + for (int i = 0, k = startIndex; i < MAX_LEVEL_SIZE; i += hashMapSize, k++) { + directory.setNodePointer(parentIndex, k, directory.getNodePointer(nodePath.nodeIndex, i)); + } + + directory.deleteNode(nodePath.nodeIndex); + + if (nodePath.parent.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxChildDepth = directory.getMaxLeftChildDepth(parentIndex); + if (maxChildDepth == localNodeDepth) + directory.setMaxLeftChildDepth(parentIndex, (byte) getMaxLevelDepth(parentIndex, 0, MAX_LEVEL_SIZE / 2)); + } else { + final int maxChildDepth = directory.getMaxRightChildDepth(parentIndex); + if (maxChildDepth == localNodeDepth) + directory.setMaxRightChildDepth(parentIndex, (byte) getMaxLevelDepth(parentIndex, MAX_LEVEL_SIZE / 2, MAX_LEVEL_SIZE)); + } + } + + private void mergeBucketsAfterDeletion(BucketPath nodePath, OHashIndexBucket bucket, OAtomicOperation atomicOperation) + throws IOException { + final int bucketDepth = bucket.getDepth(); + + if (bucket.getContentSize() > OHashIndexBucket.MAX_BUCKET_SIZE_BYTES * MERGE_THRESHOLD) + return; + + if (bucketDepth - MAX_LEVEL_DEPTH < 1) + return; + + int offset = nodePath.nodeGlobalDepth - (bucketDepth - 1); + BucketPath currentNode = nodePath; + int nodeLocalDepth = nodePath.nodeLocalDepth; + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentNode = nodePath.parent; + nodeLocalDepth = currentNode.nodeLocalDepth; + } + } + + final int diff = bucketDepth - 1 - (currentNode.nodeGlobalDepth - nodeLocalDepth); + final int interval = (1 << (nodeLocalDepth - diff - 1)); + + int firstStartIndex = currentNode.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + int firstEndIndex = firstStartIndex + interval; + + final int secondStartIndex = firstEndIndex; + final int secondEndIndex = secondStartIndex + interval; + + final OHashIndexBucket buddyBucket; + + int buddyLevel; + long buddyIndex; + long buddyPointer; + + if ((currentNode.itemIndex >>> (nodeLocalDepth - diff - 1) & 1) == 1) { + buddyPointer = directory.getNodePointer(currentNode.nodeIndex, firstStartIndex + currentNode.hashMapOffset); + + while (buddyPointer < 0) { + final int nodeIndex = (int) ((buddyPointer & Long.MAX_VALUE) >> 8); + final int itemOffset = (int) buddyPointer & 0xFF; + + buddyPointer = directory.getNodePointer(nodeIndex, itemOffset); + } + + assert buddyPointer > 0; + + buddyLevel = getFileLevel(buddyPointer); + buddyIndex = getPageIndex(buddyPointer); + } else { + buddyPointer = directory.getNodePointer(currentNode.nodeIndex, secondStartIndex + currentNode.hashMapOffset); + + while (buddyPointer < 0) { + final int nodeIndex = (int) ((buddyPointer & Long.MAX_VALUE) >> 8); + final int itemOffset = (int) buddyPointer & 0xFF; + + buddyPointer = directory.getNodePointer(nodeIndex, itemOffset); + } + + assert buddyPointer > 0; + + buddyLevel = getFileLevel(buddyPointer); + buddyIndex = getPageIndex(buddyPointer); + } + + OCacheEntry buddyCacheEntry = loadPageEntry(buddyIndex, buddyLevel, atomicOperation); + buddyCacheEntry.acquireExclusiveLock(); + try { + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + buddyBucket = new OHashIndexBucket(buddyCacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, buddyCacheEntry)); + + if (buddyBucket.getDepth() != bucketDepth) + return; + + if (bucket.mergedSize(buddyBucket) >= OHashIndexBucket.MAX_BUCKET_SIZE_BYTES) + return; + + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + metadataPage.setBucketsCount(buddyLevel, metadataPage.getBucketsCount(buddyLevel) - 2); + + int newBuddyLevel = buddyLevel - 1; + long newBuddyIndex = buddyBucket.getSplitHistory(newBuddyLevel); + + metadataPage.setBucketsCount(buddyLevel, metadataPage.getBucketsCount(buddyLevel) + 1); + + final OCacheEntry newBuddyCacheEntry = loadPageEntry(newBuddyIndex, newBuddyLevel, atomicOperation); + newBuddyCacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket newBuddyBucket = new OHashIndexBucket(bucketDepth - 1, newBuddyCacheEntry, + keySerializer, valueSerializer, keyTypes, getChanges(atomicOperation, newBuddyCacheEntry)); + + for (OHashIndexBucket.Entry entry : buddyBucket) + newBuddyBucket.appendEntry(entry.hashCode, entry.key, entry.value); + + for (OHashIndexBucket.Entry entry : bucket) + newBuddyBucket.addEntry(entry.hashCode, entry.key, entry.value); + + } finally { + newBuddyCacheEntry.releaseExclusiveLock(); + + releasePage(atomicOperation, newBuddyCacheEntry); + } + + final long bucketPointer = directory.getNodePointer(nodePath.nodeIndex, nodePath.itemIndex + nodePath.hashMapOffset); + final long bucketIndex = getPageIndex(bucketPointer); + + final long newBuddyPointer = createBucketPointer(buddyIndex, buddyLevel); + + for (int i = firstStartIndex; i < secondEndIndex; i++) + updateBucket(currentNode.nodeIndex, i, currentNode.hashMapOffset, newBuddyPointer); + + if (metadataPage.getBucketsCount(buddyLevel) > 0) { + final long newTombstoneIndex; + if (bucketIndex < buddyIndex) { + bucket.setNextRemovedBucketPair(metadataPage.getTombstoneIndex(buddyLevel)); + + newTombstoneIndex = bucketIndex; + } else { + buddyBucket.setNextRemovedBucketPair(metadataPage.getTombstoneIndex(buddyLevel)); + newTombstoneIndex = buddyIndex; + } + + metadataPage.setTombstoneIndex(buddyLevel, newTombstoneIndex); + } else + metadataPage.setTombstoneIndex(buddyLevel, -1); + + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } finally { + buddyCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, buddyCacheEntry); + } + } + + @Override + public void flush() { + acquireExclusiveLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + try { + for (int i = 0; i < HASH_CODE_SIZE; i++) { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + if (!metadataPage.isRemoved(i)) + writeCache.flush(metadataPage.getFileId(i)); + } + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + writeCache.flush(fileStateId); + directory.flush(); + + if (nullKeyIsSupported) + writeCache.flush(nullBucketFileId); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table flush"), e); + } finally { + releaseExclusiveLock(); + } + } + + @Override + public void acquireAtomicExclusiveLock() { + atomicOperationsManager.acquireExclusiveLockTillOperationComplete(this); + } + + private boolean put(K key, V value, OIndexEngine.Validator validator) { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OIndexException("Error during hash table entry put"), e); + } + acquireExclusiveLock(); + try { + + checkNullSupport(key); + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + final boolean putResult = doPut(key, value, validator, atomicOperation); + endAtomicOperation(false, null); + return putResult; + } catch (IOException e) { + rollback(); + throw OException.wrapException(new OIndexException("Error during index update"), e); + } catch (Exception e) { + rollback(); + throw OException.wrapException(new OStorageException("Error during index update"), e); + } finally { + releaseExclusiveLock(); + } + } + + @SuppressWarnings("unchecked") + private boolean doPut(K key, V value, OIndexEngine.Validator validator, OAtomicOperation atomicOperation) + throws IOException { + int sizeDiff = 0; + + if (key == null) { + boolean isNew; + OCacheEntry cacheEntry; + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + cacheEntry = addPage(atomicOperation, nullBucketFileId); + isNew = true; + } else { + cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + isNew = false; + } + + cacheEntry.acquireExclusiveLock(); + try { + ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), valueSerializer, isNew); + + final V oldValue = nullBucket.getValue(); + + if (validator != null) { + final Object result = validator.validate(null, oldValue, value); + if (result == OIndexEngine.Validator.IGNORE) + return false; + + value = (V) result; + } + + if (oldValue != null) + sizeDiff--; + + nullBucket.setValue(value); + sizeDiff++; + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + return true; + } else { + final long hashCode = keyHashFunction.hashCode(key); + + final BucketPath bucketPath = getBucket(hashCode); + final long bucketPointer = directory.getNodePointer(bucketPath.nodeIndex, bucketPath.itemIndex + bucketPath.hashMapOffset); + if (bucketPointer == 0) + throw new IllegalStateException("In this version of hash table buckets are added through split only."); + + final long pageIndex = getPageIndex(bucketPointer); + final int fileLevel = getFileLevel(bucketPointer); + + final OCacheEntry cacheEntry = loadPageEntry(pageIndex, fileLevel, atomicOperation); + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket bucket = new OHashIndexBucket(cacheEntry, keySerializer, valueSerializer, keyTypes, + getChanges(atomicOperation, cacheEntry)); + final int index = bucket.getIndex(hashCode, key); + + if (validator != null) { + final V oldValue = index > -1 ? bucket.getValue(index) : null; + final Object result = validator.validate(key, oldValue, value); + if (result == OIndexEngine.Validator.IGNORE) + return false; + + value = (V) result; + } + + if (index > -1) { + final int updateResult = bucket.updateEntry(index, value); + if (updateResult == 0) { + changeSize(sizeDiff, atomicOperation); + return true; + } + + if (updateResult == 1) { + changeSize(sizeDiff, atomicOperation); + return true; + } + + assert updateResult == -1; + + bucket.deleteEntry(index); + sizeDiff--; + } + + if (bucket.addEntry(hashCode, key, value)) { + sizeDiff++; + + changeSize(sizeDiff, atomicOperation); + return true; + } + + final BucketSplitResult splitResult = splitBucket(bucket, fileLevel, pageIndex, atomicOperation); + + final long updatedBucketPointer = splitResult.updatedBucketPointer; + final long newBucketPointer = splitResult.newBucketPointer; + final int bucketDepth = splitResult.newDepth; + + if (bucketDepth <= bucketPath.nodeGlobalDepth) { + updateNodeAfterBucketSplit(bucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } else { + if (bucketPath.nodeLocalDepth < MAX_LEVEL_DEPTH) { + final NodeSplitResult nodeSplitResult = splitNode(bucketPath); + + assert !(nodeSplitResult.allLeftHashMapsEqual && nodeSplitResult.allRightHashMapsEqual); + + final long[] newNode = nodeSplitResult.newNode; + + final int nodeLocalDepth = bucketPath.nodeLocalDepth + 1; + final int hashMapSize = 1 << nodeLocalDepth; + + assert nodeSplitResult.allRightHashMapsEqual == checkAllMapsContainSameBucket(newNode, hashMapSize); + + int newNodeIndex = -1; + if (!nodeSplitResult.allRightHashMapsEqual || bucketPath.itemIndex >= MAX_LEVEL_SIZE / 2) + newNodeIndex = directory.addNewNode((byte) 0, (byte) 0, (byte) nodeLocalDepth, newNode); + + final int updatedItemIndex = bucketPath.itemIndex << 1; + final int updatedOffset = bucketPath.hashMapOffset << 1; + final int updatedGlobalDepth = bucketPath.nodeGlobalDepth + 1; + + boolean allLeftHashMapsEqual = nodeSplitResult.allLeftHashMapsEqual; + boolean allRightHashMapsEqual = nodeSplitResult.allRightHashMapsEqual; + + if (updatedOffset < MAX_LEVEL_SIZE) { + allLeftHashMapsEqual = false; + final BucketPath updatedBucketPath = new BucketPath(bucketPath.parent, updatedOffset, updatedItemIndex, + bucketPath.nodeIndex, nodeLocalDepth, updatedGlobalDepth); + updateNodeAfterBucketSplit(updatedBucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } else { + allRightHashMapsEqual = false; + final BucketPath newBucketPath = new BucketPath(bucketPath.parent, updatedOffset - MAX_LEVEL_SIZE, updatedItemIndex, + newNodeIndex, nodeLocalDepth, updatedGlobalDepth); + updateNodeAfterBucketSplit(newBucketPath, bucketDepth, newBucketPointer, updatedBucketPointer); + } + + updateNodesAfterSplit(bucketPath, bucketPath.nodeIndex, newNode, nodeLocalDepth, hashMapSize, allLeftHashMapsEqual, + allRightHashMapsEqual, newNodeIndex); + + if (allLeftHashMapsEqual) + directory.deleteNode(bucketPath.nodeIndex); + } else { + addNewLevelNode(bucketPath, bucketPath.nodeIndex, newBucketPointer, updatedBucketPointer); + } + } + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + changeSize(sizeDiff, atomicOperation); + doPut(key, value, null /* already validated */, atomicOperation); + return true; + } + } + + private void checkNullSupport(K key) { + if (key == null && !nullKeyIsSupported) + throw new OIndexException("Null keys are not supported."); + } + + private void updateNodesAfterSplit(BucketPath bucketPath, int nodeIndex, long[] newNode, int nodeLocalDepth, int hashMapSize, + boolean allLeftHashMapEquals, boolean allRightHashMapsEquals, int newNodeIndex) throws IOException { + + final int startIndex = findParentNodeStartIndex(bucketPath); + + final int parentNodeIndex = bucketPath.parent.nodeIndex; + assert assertParentNodeStartIndex(bucketPath, directory.getNode(parentNodeIndex), startIndex); + + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - nodeLocalDepth); + if (allLeftHashMapEquals) { + for (int i = 0; i < pointersSize; i++) { + final long position = directory.getNodePointer(nodeIndex, i * hashMapSize); + directory.setNodePointer(parentNodeIndex, startIndex + i, position); + } + } else { + for (int i = 0; i < pointersSize; i++) + directory.setNodePointer(parentNodeIndex, startIndex + i, (bucketPath.nodeIndex << 8) | (i * hashMapSize) | Long.MIN_VALUE); + } + + if (allRightHashMapsEquals) { + for (int i = 0; i < pointersSize; i++) { + final long position = newNode[i * hashMapSize]; + directory.setNodePointer(parentNodeIndex, startIndex + pointersSize + i, position); + } + } else { + for (int i = 0; i < pointersSize; i++) + directory.setNodePointer(parentNodeIndex, startIndex + pointersSize + i, + (newNodeIndex << 8) | (i * hashMapSize) | Long.MIN_VALUE); + } + + updateMaxChildDepth(bucketPath.parent, bucketPath.nodeLocalDepth + 1); + } + + private void updateMaxChildDepth(BucketPath parentPath, int childDepth) throws IOException { + if (parentPath == null) + return; + + if (parentPath.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxChildDepth = directory.getMaxLeftChildDepth(parentPath.nodeIndex); + if (childDepth > maxChildDepth) + directory.setMaxLeftChildDepth(parentPath.nodeIndex, (byte) childDepth); + } else { + final int maxChildDepth = directory.getMaxRightChildDepth(parentPath.nodeIndex); + if (childDepth > maxChildDepth) + directory.setMaxRightChildDepth(parentPath.nodeIndex, (byte) childDepth); + } + } + + private boolean assertParentNodeStartIndex(BucketPath bucketPath, long[] parentNode, int calculatedIndex) { + int startIndex = -1; + for (int i = 0; i < parentNode.length; i++) + if (parentNode[i] < 0 && (parentNode[i] & Long.MAX_VALUE) >>> 8 == bucketPath.nodeIndex) { + startIndex = i; + break; + } + + return startIndex == calculatedIndex; + } + + private int findParentNodeStartIndex(BucketPath bucketPath) { + final BucketPath parentBucketPath = bucketPath.parent; + final int pointersSize = 1 << (MAX_LEVEL_DEPTH - bucketPath.nodeLocalDepth); + + if (parentBucketPath.itemIndex < MAX_LEVEL_SIZE / 2) + return (parentBucketPath.itemIndex / pointersSize) * pointersSize; + + return ((parentBucketPath.itemIndex - MAX_LEVEL_SIZE / 2) / pointersSize) * pointersSize + MAX_LEVEL_SIZE / 2; + } + + private void addNewLevelNode(BucketPath bucketPath, int nodeIndex, long newBucketPointer, long updatedBucketPointer) + throws IOException { + final int newNodeDepth; + final int newNodeStartIndex; + final int mapInterval; + + if (bucketPath.itemIndex < MAX_LEVEL_SIZE / 2) { + final int maxDepth = directory.getMaxLeftChildDepth(bucketPath.nodeIndex); + + assert getMaxLevelDepth(bucketPath.nodeIndex, 0, MAX_LEVEL_SIZE / 2) == maxDepth; + + if (maxDepth > 0) + newNodeDepth = maxDepth; + else + newNodeDepth = 1; + + mapInterval = 1 << (MAX_LEVEL_DEPTH - newNodeDepth); + newNodeStartIndex = (bucketPath.itemIndex / mapInterval) * mapInterval; + } else { + final int maxDepth = directory.getMaxRightChildDepth(bucketPath.nodeIndex); + assert getMaxLevelDepth(bucketPath.nodeIndex, MAX_LEVEL_SIZE / 2, MAX_LEVEL_SIZE) == maxDepth; + if (maxDepth > 0) + newNodeDepth = maxDepth; + else + newNodeDepth = 1; + + mapInterval = 1 << (MAX_LEVEL_DEPTH - newNodeDepth); + newNodeStartIndex = ((bucketPath.itemIndex - MAX_LEVEL_SIZE / 2) / mapInterval) * mapInterval + MAX_LEVEL_SIZE / 2; + } + + final int newNodeIndex = directory.addNewNode((byte) 0, (byte) 0, (byte) newNodeDepth, new long[MAX_LEVEL_SIZE]); + + final int mapSize = 1 << newNodeDepth; + for (int i = 0; i < mapInterval; i++) { + final int nodeOffset = i + newNodeStartIndex; + final long bucketPointer = directory.getNodePointer(nodeIndex, nodeOffset); + + if (nodeOffset != bucketPath.itemIndex) { + for (int n = i << newNodeDepth; n < (i + 1) << newNodeDepth; n++) + directory.setNodePointer(newNodeIndex, n, bucketPointer); + } else { + for (int n = i << newNodeDepth; n < (2 * i + 1) << (newNodeDepth - 1); n++) + directory.setNodePointer(newNodeIndex, n, updatedBucketPointer); + + for (int n = (2 * i + 1) << (newNodeDepth - 1); n < (i + 1) << newNodeDepth; n++) + directory.setNodePointer(newNodeIndex, n, newBucketPointer); + } + + directory.setNodePointer(nodeIndex, nodeOffset, (newNodeIndex << 8) | (i * mapSize) | Long.MIN_VALUE); + } + + updateMaxChildDepth(bucketPath, newNodeDepth); + } + + private int getMaxLevelDepth(int nodeIndex, int start, int end) throws IOException { + int currentIndex = -1; + int maxDepth = 0; + + for (int i = start; i < end; i++) { + final long nodePosition = directory.getNodePointer(nodeIndex, i); + if (nodePosition >= 0) + continue; + + final int index = (int) ((nodePosition & Long.MAX_VALUE) >>> 8); + if (index == currentIndex) + continue; + + currentIndex = index; + + final int nodeLocalDepth = directory.getNodeLocalDepth(index); + if (maxDepth < nodeLocalDepth) + maxDepth = nodeLocalDepth; + } + + return maxDepth; + } + + private void updateNodeAfterBucketSplit(BucketPath bucketPath, int bucketDepth, long newBucketPointer, long updatedBucketPointer) + throws IOException { + int offset = bucketPath.nodeGlobalDepth - (bucketDepth - 1); + BucketPath currentNode = bucketPath; + int nodeLocalDepth = bucketPath.nodeLocalDepth; + while (offset > 0) { + offset -= nodeLocalDepth; + if (offset > 0) { + currentNode = bucketPath.parent; + nodeLocalDepth = currentNode.nodeLocalDepth; + } + } + + final int diff = bucketDepth - 1 - (currentNode.nodeGlobalDepth - nodeLocalDepth); + + final int interval = (1 << (nodeLocalDepth - diff - 1)); + final int firstStartIndex = currentNode.itemIndex & ((LEVEL_MASK << (nodeLocalDepth - diff)) & LEVEL_MASK); + final int firstEndIndex = firstStartIndex + interval; + + final int secondStartIndex = firstEndIndex; + final int secondEndIndex = secondStartIndex + interval; + + for (int i = firstStartIndex; i < firstEndIndex; i++) + updateBucket(currentNode.nodeIndex, i, currentNode.hashMapOffset, updatedBucketPointer); + + for (int i = secondStartIndex; i < secondEndIndex; i++) + updateBucket(currentNode.nodeIndex, i, currentNode.hashMapOffset, newBucketPointer); + } + + private boolean checkAllMapsContainSameBucket(long[] newNode, int hashMapSize) { + int n = 0; + boolean allHashMapsEquals = true; + while (n < newNode.length) { + boolean allHashBucketEquals = true; + for (int i = 0; i < hashMapSize - 1; i++) { + if (newNode[i + n] != newNode[i + n + 1]) { + allHashBucketEquals = false; + break; + } + } + n += hashMapSize; + if (!allHashBucketEquals) { + allHashMapsEquals = false; + break; + } + } + + assert assertAllNodesAreFilePointers(allHashMapsEquals, newNode, hashMapSize); + + return allHashMapsEquals; + } + + private boolean assertAllNodesAreFilePointers(boolean allHashMapsEquals, long[] newNode, int hashMapSize) { + if (allHashMapsEquals) { + int n = 0; + while (n < newNode.length) { + for (int i = 0; i < hashMapSize; i++) { + if (newNode[i] < 0) { + return false; + } + } + n += hashMapSize; + } + } + + return true; + } + + private NodeSplitResult splitNode(BucketPath bucketPath) throws IOException { + final long[] newNode = new long[MAX_LEVEL_SIZE]; + final int hashMapSize = 1 << (bucketPath.nodeLocalDepth + 1); + + boolean hashMapItemsAreEqual = true; + boolean allLeftItemsAreEqual; + boolean allRightItemsAreEqual; + + int mapCounter = 0; + long firstPosition = -1; + + long[] node = directory.getNode(bucketPath.nodeIndex); + + for (int i = MAX_LEVEL_SIZE / 2; i < MAX_LEVEL_SIZE; i++) { + final long position = node[i]; + if (hashMapItemsAreEqual && mapCounter == 0) + firstPosition = position; + + newNode[2 * (i - MAX_LEVEL_SIZE / 2)] = position; + newNode[2 * (i - MAX_LEVEL_SIZE / 2) + 1] = position; + + if (hashMapItemsAreEqual) { + hashMapItemsAreEqual = firstPosition == position; + mapCounter += 2; + + if (mapCounter >= hashMapSize) + mapCounter = 0; + } + } + + mapCounter = 0; + allRightItemsAreEqual = hashMapItemsAreEqual; + + hashMapItemsAreEqual = true; + final long[] updatedNode = new long[node.length]; + for (int i = 0; i < MAX_LEVEL_SIZE / 2; i++) { + final long position = node[i]; + if (hashMapItemsAreEqual && mapCounter == 0) + firstPosition = position; + + updatedNode[2 * i] = position; + updatedNode[2 * i + 1] = position; + + if (hashMapItemsAreEqual) { + hashMapItemsAreEqual = firstPosition == position; + + mapCounter += 2; + + if (mapCounter >= hashMapSize) + mapCounter = 0; + } + } + + allLeftItemsAreEqual = hashMapItemsAreEqual; + + directory.setNode(bucketPath.nodeIndex, updatedNode); + directory.setNodeLocalDepth(bucketPath.nodeIndex, (byte) (directory.getNodeLocalDepth(bucketPath.nodeIndex) + 1)); + + return new NodeSplitResult(newNode, allLeftItemsAreEqual, allRightItemsAreEqual); + } + + private void splitBucketContent(OHashIndexBucket bucket, OHashIndexBucket updatedBucket, + OHashIndexBucket newBucket, int newBucketDepth) throws IOException { + assert checkBucketDepth(bucket); + + for (OHashIndexBucket.Entry entry : bucket) { + if (((keyHashFunction.hashCode(entry.key) >>> (HASH_CODE_SIZE - newBucketDepth)) & 1) == 0) + updatedBucket.appendEntry(entry.hashCode, entry.key, entry.value); + else + newBucket.appendEntry(entry.hashCode, entry.key, entry.value); + } + + updatedBucket.setDepth(newBucketDepth); + newBucket.setDepth(newBucketDepth); + + assert checkBucketDepth(updatedBucket); + assert checkBucketDepth(newBucket); + } + + private BucketSplitResult splitBucket(OHashIndexBucket bucket, int fileLevel, long pageIndex, + OAtomicOperation atomicOperation) throws IOException { + int bucketDepth = bucket.getDepth(); + int newBucketDepth = bucketDepth + 1; + + final int newFileLevel = newBucketDepth - MAX_LEVEL_DEPTH; + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + + if (metadataPage.isRemoved(newFileLevel)) + createFileMetadata(newFileLevel, metadataPage, atomicOperation); + + final long tombstoneIndex = metadataPage.getTombstoneIndex(newFileLevel); + + final long updatedBucketIndex; + + if (tombstoneIndex >= 0) { + final OCacheEntry tombstoneCacheEntry = loadPageEntry(tombstoneIndex, newFileLevel, atomicOperation); + try { + final OHashIndexBucket tombstone = new OHashIndexBucket(tombstoneCacheEntry, keySerializer, valueSerializer, + keyTypes, getChanges(atomicOperation, tombstoneCacheEntry)); + metadataPage.setTombstoneIndex(newFileLevel, tombstone.getNextRemovedBucketPair()); + + updatedBucketIndex = tombstoneIndex; + } finally { + releasePage(atomicOperation, tombstoneCacheEntry); + } + } else + updatedBucketIndex = getFilledUpTo(atomicOperation, metadataPage.getFileId(newFileLevel)); + + final long newBucketIndex = updatedBucketIndex + 1; + + final OCacheEntry updatedBucketCacheEntry = loadPageEntry(updatedBucketIndex, newFileLevel, atomicOperation); + updatedBucketCacheEntry.acquireExclusiveLock(); + try { + final OCacheEntry newBucketCacheEntry = loadPageEntry(newBucketIndex, newFileLevel, atomicOperation); + + newBucketCacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket updatedBucket = new OHashIndexBucket(newBucketDepth, updatedBucketCacheEntry, + keySerializer, valueSerializer, keyTypes, getChanges(atomicOperation, updatedBucketCacheEntry)); + final OHashIndexBucket newBucket = new OHashIndexBucket(newBucketDepth, newBucketCacheEntry, keySerializer, + valueSerializer, keyTypes, getChanges(atomicOperation, newBucketCacheEntry)); + + splitBucketContent(bucket, updatedBucket, newBucket, newBucketDepth); + + assert bucket.getDepth() == bucketDepth; + + metadataPage.setBucketsCount(fileLevel, metadataPage.getBucketsCount(fileLevel) - 1); + + assert metadataPage.getBucketsCount(fileLevel) >= 0; + + updatedBucket.setSplitHistory(fileLevel, pageIndex); + newBucket.setSplitHistory(fileLevel, pageIndex); + + metadataPage.setBucketsCount(newFileLevel, metadataPage.getBucketsCount(newFileLevel) + 2); + + final long updatedBucketPointer = createBucketPointer(updatedBucketIndex, newFileLevel); + final long newBucketPointer = createBucketPointer(newBucketIndex, newFileLevel); + + return new BucketSplitResult(updatedBucketPointer, newBucketPointer, newBucketDepth); + } finally { + newBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, newBucketCacheEntry); + } + } finally { + updatedBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, updatedBucketCacheEntry); + } + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } + + private boolean checkBucketDepth(OHashIndexBucket bucket) { + int bucketDepth = bucket.getDepth(); + + if (bucket.size() == 0) + return true; + + final Iterator> positionIterator = bucket.iterator(); + + long firstValue = keyHashFunction.hashCode(positionIterator.next().key) >>> (HASH_CODE_SIZE - bucketDepth); + while (positionIterator.hasNext()) { + final long value = keyHashFunction.hashCode(positionIterator.next().key) >>> (HASH_CODE_SIZE - bucketDepth); + if (value != firstValue) + return false; + } + + return true; + } + + private void updateBucket(int nodeIndex, int itemIndex, int offset, long newBucketPointer) throws IOException { + final long position = directory.getNodePointer(nodeIndex, itemIndex + offset); + if (position >= 0) + directory.setNodePointer(nodeIndex, itemIndex + offset, newBucketPointer); + else { + final int childNodeIndex = (int) ((position & Long.MAX_VALUE) >>> 8); + final int childOffset = (int) (position & 0xFF); + final int childNodeDepth = directory.getNodeLocalDepth(childNodeIndex); + final int interval = 1 << childNodeDepth; + for (int i = 0; i < interval; i++) { + updateBucket(childNodeIndex, i, childOffset, newBucketPointer); + } + } + } + + @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") + private void initHashTreeState(OAtomicOperation atomicOperation) throws IOException { + + for (long pageIndex = 0; pageIndex < MAX_LEVEL_SIZE; pageIndex++) { + final OCacheEntry cacheEntry = loadPageEntry(pageIndex, 0, atomicOperation); + cacheEntry.acquireExclusiveLock(); + try { + final OHashIndexBucket emptyBucket = new OHashIndexBucket(MAX_LEVEL_DEPTH, cacheEntry, keySerializer, + valueSerializer, keyTypes, getChanges(atomicOperation, cacheEntry)); + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + final long[] rootTree = new long[MAX_LEVEL_SIZE]; + for (int i = 0; i < MAX_LEVEL_SIZE; i++) + rootTree[i] = createBucketPointer(i, 0); + + directory.clear(); + directory.addNewNode((byte) 0, (byte) 0, (byte) MAX_LEVEL_DEPTH, rootTree); + + OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + hashStateEntry.acquireExclusiveLock(); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + metadataPage.setBucketsCount(0, MAX_LEVEL_SIZE); + metadataPage.setRecordsCount(0); + } finally { + hashStateEntry.releaseExclusiveLock(); + releasePage(atomicOperation, hashStateEntry); + } + } + + private long createBucketPointer(long pageIndex, int fileLevel) { + return ((pageIndex + 1) << 8) | fileLevel; + } + + private long getPageIndex(long bucketPointer) { + return (bucketPointer >>> 8) - 1; + } + + private int getFileLevel(long bucketPointer) { + return (int) (bucketPointer & 0xFF); + } + + private OCacheEntry loadPageEntry(long pageIndex, int fileLevel, OAtomicOperation atomicOperation) throws IOException { + final long fileId; + final OCacheEntry hashStateEntry = loadPage(atomicOperation, fileStateId, hashStateEntryIndex, true); + try { + OHashIndexFileLevelMetadataPage metadataPage = new OHashIndexFileLevelMetadataPage(hashStateEntry, + getChanges(atomicOperation, hashStateEntry), false); + fileId = metadataPage.getFileId(fileLevel); + } finally { + releasePage(atomicOperation, hashStateEntry); + } + + OCacheEntry entry = loadPage(atomicOperation, fileId, pageIndex, false); + if (entry == null) + entry = addPage(atomicOperation, fileId); + + return entry; + } + + private BucketPath getBucket(final long hashCode) throws IOException { + int localNodeDepth = directory.getNodeLocalDepth(0); + int nodeDepth = localNodeDepth; + BucketPath parentNode = null; + int nodeIndex = 0; + int offset = 0; + + int index = (int) ((hashCode >>> (HASH_CODE_SIZE - nodeDepth)) & (LEVEL_MASK >>> (MAX_LEVEL_DEPTH - localNodeDepth))); + BucketPath currentNode = new BucketPath(null, 0, index, 0, localNodeDepth, nodeDepth); + do { + final long position = directory.getNodePointer(nodeIndex, index + offset); + if (position >= 0) + return currentNode; + + nodeIndex = (int) ((position & Long.MAX_VALUE) >>> 8); + offset = (int) (position & 0xFF); + + localNodeDepth = directory.getNodeLocalDepth(nodeIndex); + nodeDepth += localNodeDepth; + + index = (int) ((hashCode >>> (HASH_CODE_SIZE - nodeDepth)) & (LEVEL_MASK >>> (MAX_LEVEL_DEPTH - localNodeDepth))); + + parentNode = currentNode; + currentNode = new BucketPath(parentNode, offset, index, nodeIndex, localNodeDepth, nodeDepth); + } while (nodeDepth <= HASH_CODE_SIZE); + + throw new IllegalStateException("Extendible hashing tree in corrupted state."); + } + + @Override + protected void startOperation() { + OSessionStoragePerformanceStatistic sessionStoragePerformanceStatistic = performanceStatisticManager + .getSessionPerformanceStatistic(); + if (sessionStoragePerformanceStatistic != null) { + sessionStoragePerformanceStatistic + .startComponentOperation(getFullName(), OSessionStoragePerformanceStatistic.ComponentType.INDEX); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OMurmurHash3HashFunction.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OMurmurHash3HashFunction.java new file mode 100755 index 00000000000..d964f2ef3d8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/OMurmurHash3HashFunction.java @@ -0,0 +1,50 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.hashindex.local; + +import com.orientechnologies.common.hash.OMurmurHash3; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * @author Andrey Lomakin + * @since 12.03.13 + */ +@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") +public class OMurmurHash3HashFunction implements OHashFunction { + private static final int SEED = 362498820; + + private OBinarySerializer valueSerializer; + + public OBinarySerializer getValueSerializer() { + return valueSerializer; + } + + public void setValueSerializer(OBinarySerializer valueSerializer) { + this.valueSerializer = valueSerializer; + } + + @Override + public long hashCode(final V value) { + final byte[] serializedValue = new byte[valueSerializer.getObjectSize(value)]; + valueSerializer.serializeNativeObject(value, serializedValue, 0); + return OMurmurHash3.murmurHash3_x64_64(serializedValue, SEED); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ONullBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ONullBucket.java new file mode 100755 index 00000000000..5e8e145ca0f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/hashindex/local/ONullBucket.java @@ -0,0 +1,66 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.hashindex.local; + +import java.io.IOException; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/25/14 + */ +public class ONullBucket extends ODurablePage { + private final OBinarySerializer valueSerializer; + + public ONullBucket(OCacheEntry cacheEntry, OWALChanges changes, OBinarySerializer valueSerializer, boolean isNew) { + super(cacheEntry, changes); + this.valueSerializer = valueSerializer; + + if (isNew) + setByteValue(NEXT_FREE_POSITION, (byte) 0); + } + + public void setValue(V value) throws IOException { + setByteValue(NEXT_FREE_POSITION, (byte) 1); + + final int valueSize = valueSerializer.getObjectSize(value); + + final byte[] serializedValue = new byte[valueSize]; + valueSerializer.serializeNativeObject(value, serializedValue, 0); + + setBinaryValue(NEXT_FREE_POSITION + 1, serializedValue); + } + + public V getValue() { + if (getByteValue(NEXT_FREE_POSITION) == 0) + return null; + + return deserializeFromDirectMemory(valueSerializer, NEXT_FREE_POSITION + 1); + } + + public void removeValue() { + setByteValue(NEXT_FREE_POSITION, (byte) 0); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OSBTreeMapEntryIterator.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OSBTreeMapEntryIterator.java new file mode 100755 index 00000000000..732068ca6b1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OSBTreeMapEntryIterator.java @@ -0,0 +1,120 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree; + +import java.util.*; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainer; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSBTreeMapEntryIterator implements Iterator> { + private LinkedList> preFetchedValues; + private final OTreeInternal sbTree; + private K firstKey; + private Map.Entry currentEntry; + + private final int prefetchSize; + + public OSBTreeMapEntryIterator(OTreeInternal sbTree) { + this(sbTree, 8000); + } + + public OSBTreeMapEntryIterator(OTreeInternal sbTree, int prefetchSize) { + this.sbTree = sbTree; + this.prefetchSize = prefetchSize; + + if (sbTree.size() == 0) { + this.preFetchedValues = null; + return; + } + + this.preFetchedValues = new LinkedList>(); + firstKey = sbTree.firstKey(); + + prefetchData(true); + } + + private void prefetchData(boolean firstTime) { + sbTree.loadEntriesMajor(firstKey, firstTime, true, new OTreeInternal.RangeResultListener() { + @Override + public boolean addResult(final Map.Entry entry) { + final V value = entry.getValue(); + final V resultValue; + if (value instanceof OIndexRIDContainer) + resultValue = (V) new HashSet((Collection) value); + else + resultValue = value; + + preFetchedValues.add(new Map.Entry() { + @Override + public K getKey() { + return entry.getKey(); + } + + @Override + public V getValue() { + return resultValue; + } + + @Override + public V setValue(V v) { + throw new UnsupportedOperationException("setValue"); + } + }); + + return preFetchedValues.size() <= prefetchSize; + } + }); + + if (preFetchedValues.isEmpty()) + preFetchedValues = null; + else + firstKey = preFetchedValues.getLast().getKey(); + } + + @Override + public boolean hasNext() { + return preFetchedValues != null; + } + + @Override + public Map.Entry next() { + if (!hasNext()) + throw new NoSuchElementException(); + + final Map.Entry entry = preFetchedValues.removeFirst(); + if (preFetchedValues.isEmpty()) + prefetchData(false); + + currentEntry = entry; + + return entry; + } + + @Override + public void remove() { + sbTree.remove(currentEntry.getKey()); + currentEntry = null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OTreeInternal.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OTreeInternal.java new file mode 100755 index 00000000000..a88e6861221 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/OTreeInternal.java @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public interface OTreeInternal { + long size(); + + void loadEntriesMajor(K key, boolean inclusive, boolean ascSortOrder, RangeResultListener listener); + + K firstKey(); + + V remove(K key); + + /** + * @author Artem Orobets (enisher-at-gmail.com) + */ + interface RangeResultListener { + /** + * Callback method for result entries. + * + * @param entry + * result entry + * @return true if continue to iterate through entries, false if no more result needed. + */ + boolean addResult(Map.Entry entry); + } + + public class AccumulativeListener implements RangeResultListener { + private final int limit; + private List> entries; + + public AccumulativeListener(int limit) { + entries = new ArrayList>(limit); + this.limit = limit; + } + + @Override + public boolean addResult(Map.Entry entry) { + entries.add(entry); + + return limit > entries.size(); + } + + public List> getResult() { + return entries; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/ONullBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/ONullBucket.java new file mode 100755 index 00000000000..67e23d9eeac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/ONullBucket.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.index.sbtree.local; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; + +/** + * + * Bucket which is intended to save values stored in sbtree under null key. Bucket has following layout: + *
        + *
      1. First byte is flag which indicates presence of value in bucket
      2. + *
      3. Second byte indicates whether value is presented by link to the "bucket list" where actual value is stored or real value + * passed be user.
      4. + *
      5. The rest is serialized value whether link or passed in value.
      6. + *
      + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 4/15/14 + */ +public class ONullBucket extends ODurablePage { + private final OBinarySerializer valueSerializer; + + public ONullBucket(OCacheEntry cacheEntry, OWALChanges changes, OBinarySerializer valueSerializer, boolean isNew) { + super(cacheEntry, changes); + this.valueSerializer = valueSerializer; + + if (isNew) + setByteValue(NEXT_FREE_POSITION, (byte) 0); + } + + public void setValue(OSBTreeValue value) throws IOException { + setByteValue(NEXT_FREE_POSITION, (byte) 1); + + if (value.isLink()) { + setByteValue(NEXT_FREE_POSITION + 1, (byte) 0); + setLongValue(NEXT_FREE_POSITION + 2, value.getLink()); + } else { + final int valueSize = valueSerializer.getObjectSize(value.getValue()); + + final byte[] serializedValue = new byte[valueSize]; + valueSerializer.serializeNativeObject(value.getValue(), serializedValue, 0); + + setByteValue(NEXT_FREE_POSITION + 1, (byte) 1); + setBinaryValue(NEXT_FREE_POSITION + 2, serializedValue); + } + } + + public OSBTreeValue getValue() { + if (getByteValue(NEXT_FREE_POSITION) == 0) + return null; + + final boolean isLink = getByteValue(NEXT_FREE_POSITION + 1) == 0; + if (isLink) + return new OSBTreeValue(true, getLongValue(NEXT_FREE_POSITION + 2), null); + + return new OSBTreeValue(false, -1, deserializeFromDirectMemory(valueSerializer, NEXT_FREE_POSITION + 2)); + } + + public void removeValue() { + setByteValue(NEXT_FREE_POSITION, (byte) 0); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTree.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTree.java new file mode 100755 index 00000000000..bd81fb9016a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTree.java @@ -0,0 +1,2262 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OTooBigIndexKeyException; +import com.orientechnologies.orient.core.index.OAlwaysGreaterKey; +import com.orientechnologies.orient.core.index.OAlwaysLessKey; +import com.orientechnologies.orient.core.index.OCompositeKey; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.iterator.OEmptyMapEntryIterator; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; +import com.orientechnologies.orient.core.storage.impl.local.statistic.OSessionStoragePerformanceStatistic; + +import java.io.IOException; +import java.util.*; + +/** + * This is implementation which is based on B+-tree implementation threaded tree. + *

      + * The main differences are: + *

        + *
      1. Buckets are not compacted/removed if they are empty after deletion of item. They reused later when new items are added.
      2. + *
      3. All non-leaf buckets have links to neighbor buckets which contain keys which are less/more than keys contained in current + * bucket
      4. + *
          + *

          + *

          + * There is support of null values for keys, but values itself cannot be null. Null keys support is switched off by default if null + * keys are supported value which is related to null key will be stored in separate file which has only one page. + *

          + * Buckets/pages for usual (non-null) key-value entries can be considered as sorted array. The first bytes of page contains such + * auxiliary information as size of entries contained in bucket, links to neighbors which contain entries with keys less/more than + * keys in current bucket. + *

          + * The next bytes contain sorted array of entries. Array itself is split on two parts. First part is growing from start to end, and + * second part is growing from end to start. + *

          + * First part is array of offsets to real key-value entries which are stored in second part of array which grows from end to start. + * This array of offsets is sorted by accessing order according to key value. So we can use binary search to find requested key. + * When new key-value pair is added we append binary presentation of this pair to the second part of array which grows from end of + * page to start, remember value of offset for this pair, and find proper position of this offset inside of first part of array. + * Such approach allows to minimize amount of memory involved in performing of operations and as result speed up data processing. + * + * @author Andrey Lomakin + * @since 8/7/13 + */ +public class OSBTree extends ODurableComponent { + private static final int MAX_KEY_SIZE = OGlobalConfiguration.SBTREE_MAX_KEY_SIZE.getValueAsInteger(); + private static final int MAX_EMBEDDED_VALUE_SIZE = OGlobalConfiguration.SBTREE_MAX_EMBEDDED_VALUE_SIZE + .getValueAsInteger(); + private static final OAlwaysLessKey ALWAYS_LESS_KEY = new OAlwaysLessKey(); + private static final OAlwaysGreaterKey ALWAYS_GREATER_KEY = new OAlwaysGreaterKey(); + + private static final int MAX_PATH_LENGTH = OGlobalConfiguration.SBTREE_MAX_DEPTH.getValueAsInteger(); + + private final static long ROOT_INDEX = 0; + private final Comparator comparator = ODefaultComparator.INSTANCE; + private final String nullFileExtension; + private final boolean durableInNonTxMode; + private long fileId; + private long nullBucketFileId = -1; + private int keySize; + private OBinarySerializer keySerializer; + private OType[] keyTypes; + private OBinarySerializer valueSerializer; + private boolean nullPointerSupport; + + public OSBTree(String name, String dataFileExtension, boolean durableInNonTxMode, String nullFileExtension, + OAbstractPaginatedStorage storage) { + super(storage, name, dataFileExtension, name + dataFileExtension); + acquireExclusiveLock(); + try { + this.nullFileExtension = nullFileExtension; + this.durableInNonTxMode = durableInNonTxMode; + } finally { + releaseExclusiveLock(); + } + } + + public void create(OBinarySerializer keySerializer, OBinarySerializer valueSerializer, OType[] keyTypes, int keySize, + boolean nullPointerSupport) { + assert keySerializer != null; + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree creation", this), e); + } + + acquireExclusiveLock(); + try { + + this.keySize = keySize; + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.keySerializer = keySerializer; + + this.valueSerializer = valueSerializer; + this.nullPointerSupport = nullPointerSupport; + + fileId = addFile(atomicOperation, getFullName()); + + if (nullPointerSupport) + nullBucketFileId = addFile(atomicOperation, getName() + nullFileExtension); + + OCacheEntry rootCacheEntry = addPage(atomicOperation, fileId); + rootCacheEntry.acquireExclusiveLock(); + try { + + OSBTreeBucket rootBucket = new OSBTreeBucket(rootCacheEntry, true, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + rootBucket.setTreeSize(0); + + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + try { + endAtomicOperation(true, e); + } catch (IOException e1) { + OLogManager.instance().error(this, "Error during sbtree data rollback", e1); + } + throw OException.wrapException(new OSBTreeException("Error creation of sbtree with name " + getName(), this), e); + } catch (RuntimeException e) { + try { + endAtomicOperation(true, e); + } catch (IOException e1) { + OLogManager.instance().error(this, "Error during sbtree data rollback", e1); + } + throw e; + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public boolean isNullPointerSupport() { + acquireSharedLock(); + try { + return nullPointerSupport; + } finally { + releaseSharedLock(); + } + } + + public V get(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + checkNullSupport(key); + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + if (key != null) { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + if (bucketSearchResult.itemIndex < 0) + return null; + + long pageIndex = bucketSearchResult.getLastPathItem(); + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + keyBucketCacheEntry.acquireSharedLock(); + try { + OSBTreeBucket keyBucket = new OSBTreeBucket(keyBucketCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, keyBucketCacheEntry)); + + OSBTreeBucket.SBTreeEntry treeEntry = keyBucket.getEntry(bucketSearchResult.itemIndex); + return readValue(treeEntry.value, atomicOperation); + } finally { + keyBucketCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + } + } else { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) + return null; + + final OCacheEntry nullBucketCacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + nullBucketCacheEntry.acquireSharedLock(); + try { + final ONullBucket nullBucket = new ONullBucket(nullBucketCacheEntry, + getChanges(atomicOperation, nullBucketCacheEntry), valueSerializer, false); + final OSBTreeValue treeValue = nullBucket.getValue(); + if (treeValue == null) + return null; + + return readValue(treeValue, atomicOperation); + } finally { + nullBucketCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, nullBucketCacheEntry); + } + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during retrieving of sbtree with name " + getName(), this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.startIndexEntryReadTimer(); + completeOperation(); + } + } + + public void put(K key, V value) { + put(key, value, null); + } + + /** + * Puts the given value under the given key into this tree. Validates the operation using the provided validator. + * + * @param key the key to put the value under. + * @param value the value to put. + * @param validator the operation validator. + * + * @return {@code true} if the validator allowed the put, {@code false} otherwise. + * + * @see OIndexEngine.Validator#validate(Object, Object, Object) + */ + public boolean validatedPut(K key, V value, OIndexEngine.Validator validator) { + return put(key, value, validator); + } + + public void close(boolean flush) { + startOperation(); + try { + acquireExclusiveLock(); + try { + readCache.closeFile(fileId, flush, writeCache); + + if (nullPointerSupport) + readCache.closeFile(nullBucketFileId, flush, writeCache); + + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during close of index " + getName(), this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void close() { + close(true); + } + + public void clear() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree clear", this), e); + } + + acquireExclusiveLock(); + try { + truncateFile(atomicOperation, fileId); + + if (nullPointerSupport) + truncateFile(atomicOperation, nullBucketFileId); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + if (cacheEntry == null) { + cacheEntry = addPage(atomicOperation, fileId); + } + + cacheEntry.acquireExclusiveLock(); + try { + OSBTreeBucket rootBucket = new OSBTreeBucket(cacheEntry, true, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + rootBucket.setTreeSize(0); + + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + + throw OException.wrapException(new OSBTreeException("Error during clear of sbtree with name " + getName(), this), e); + } catch (RuntimeException e) { + rollback(e); + throw e; + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void delete() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree deletion", this), e); + } + + acquireExclusiveLock(); + try { + deleteFile(atomicOperation, fileId); + + if (nullPointerSupport) + deleteFile(atomicOperation, nullBucketFileId); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OSBTreeException("Error during delete of sbtree with name " + getName(), this), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OSBTreeException("Error during delete of sbtree with name " + getName(), this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void deleteWithoutLoad(String name) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree deletion", this), e); + } + + acquireExclusiveLock(); + try { + if (isFileExists(atomicOperation, getFullName())) { + final long fileId = openFile(atomicOperation, getFullName()); + deleteFile(atomicOperation, fileId); + } + + if (isFileExists(atomicOperation, getName() + nullFileExtension)) { + final long nullFileId = openFile(atomicOperation, getName() + nullFileExtension); + deleteFile(atomicOperation, nullFileId); + } + + endAtomicOperation(false, null); + } catch (IOException ioe) { + rollback(ioe); + throw OException.wrapException(new OSBTreeException("Exception during deletion of sbtree " + getName(), this), ioe); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OSBTreeException("Exception during deletion of sbtree " + getName(), this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public void load(String name, OBinarySerializer keySerializer, OBinarySerializer valueSerializer, OType[] keyTypes, + int keySize, boolean nullPointerSupport) { + startOperation(); + try { + acquireExclusiveLock(); + try { + this.keySize = keySize; + if (keyTypes != null) + this.keyTypes = Arrays.copyOf(keyTypes, keyTypes.length); + else + this.keyTypes = null; + + this.nullPointerSupport = nullPointerSupport; + + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + fileId = openFile(atomicOperation, getFullName()); + if (nullPointerSupport) + nullBucketFileId = openFile(atomicOperation, name + nullFileExtension); + + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Exception during loading of sbtree " + name, this), e); + } finally { + releaseExclusiveLock(); + } + } finally { + completeOperation(); + } + } + + public long size() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + rootCacheEntry.acquireSharedLock(); + try { + OSBTreeBucket rootBucket = new OSBTreeBucket(rootCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + return rootBucket.getTreeSize(); + } finally { + rootCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, rootCacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during retrieving of size of index " + getName(), this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + public V remove(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryDeletionTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree entrie remove", this), e); + } + + acquireExclusiveLock(); + try { + V removedValue; + + if (key != null) { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + if (bucketSearchResult.itemIndex < 0) { + endAtomicOperation(false, null); + return null; + } + + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketSearchResult.getLastPathItem(), false); + keyBucketCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBucket keyBucket = new OSBTreeBucket(keyBucketCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, keyBucketCacheEntry)); + + final OSBTreeValue removed = keyBucket.getEntry(bucketSearchResult.itemIndex).value; + final V value = readValue(removed, atomicOperation); + + long removedValueLink = keyBucket.remove(bucketSearchResult.itemIndex); + if (removedValueLink >= 0) + removeLinkedValue(removedValueLink, atomicOperation); + + setSize(size() - 1, atomicOperation); + + removedValue = value; + } finally { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + } + } else { + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + endAtomicOperation(false, null); + return null; + } + + OCacheEntry nullCacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + nullCacheEntry.acquireExclusiveLock(); + try { + ONullBucket nullBucket = new ONullBucket(nullCacheEntry, getChanges(atomicOperation, nullCacheEntry), + valueSerializer, false); + OSBTreeValue treeValue = nullBucket.getValue(); + + if (treeValue != null) { + removedValue = readValue(treeValue, atomicOperation); + nullBucket.removeValue(); + } else + removedValue = null; + } finally { + nullCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, nullCacheEntry); + } + + if (removedValue != null) + setSize(size() - 1, atomicOperation); + } + + endAtomicOperation(false, null); + return removedValue; + } catch (IOException e) { + rollback(e); + + throw OException + .wrapException(new OSBTreeException("Error during removing key " + key + " from sbtree " + getName(), this), e); + } catch (RuntimeException e) { + rollback(e); + throw e; + } finally { + releaseExclusiveLock(); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryDeletionTimer(); + completeOperation(); + } + } + + public OSBTreeCursor iterateEntriesMinor(K key, boolean inclusive, boolean ascSortOrder) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + if (!ascSortOrder) + return iterateEntriesMinorDesc(key, inclusive, atomicOperation); + + return iterateEntriesMinorAsc(key, inclusive, atomicOperation); + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException( + new OSBTreeException("Error during iteration of minor values for key " + key + " in sbtree " + getName(), this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public OSBTreeCursor iterateEntriesMajor(K key, boolean inclusive, boolean ascSortOrder) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + if (ascSortOrder) + return iterateEntriesMajorAsc(key, inclusive, atomicOperation); + + return iterateEntriesMajorDesc(key, inclusive, atomicOperation); + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException( + new OSBTreeException("Error during iteration of major values for key " + key + " in sbtree " + getName(), this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public K firstKey() { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + final BucketSearchResult searchResult = firstItem(atomicOperation); + if (searchResult == null) + return null; + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, searchResult.getLastPathItem(), false); + cacheEntry.acquireSharedLock(); + try { + OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + return bucket.getKey(searchResult.itemIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeException("Error during finding first key in sbtree [" + getName() + "]", this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public K lastKey() { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + final BucketSearchResult searchResult = lastItem(atomicOperation); + if (searchResult == null) + return null; + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, searchResult.getLastPathItem(), false); + cacheEntry.acquireSharedLock(); + try { + OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + return bucket.getKey(searchResult.itemIndex); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeException("Error during finding last key in sbtree [" + getName() + "]", this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public OSBTreeKeyCursor keyCursor() { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + final BucketSearchResult searchResult = firstItem(atomicOperation); + if (searchResult == null) + return new OSBTreeKeyCursor() { + @Override + public K next(int prefetchSize) { + return null; + } + }; + + return new OSBTreeFullKeyCursor(searchResult.getLastPathItem()); + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeException("Error during finding first key in sbtree [" + getName() + "]", this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public OSBTreeCursor iterateEntriesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, + boolean ascSortOrder) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + if (ascSortOrder) + return iterateEntriesBetweenAscOrder(keyFrom, fromInclusive, keyTo, toInclusive, atomicOperation); + else + return iterateEntriesBetweenDescOrder(keyFrom, fromInclusive, keyTo, toInclusive, atomicOperation); + } finally { + releaseSharedLock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OSBTreeException( + "Error during fetch of values between key " + keyFrom + " and key " + keyTo + " in sbtree " + getName(), this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + + public void flush() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + acquireSharedLock(); + try { + writeCache.flush(); + } finally { + releaseSharedLock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + /** + * Acquires exclusive lock in the active atomic operation running on the current thread for this SB-tree. + */ + public void acquireAtomicExclusiveLock() { + atomicOperationsManager.acquireExclusiveLockTillOperationComplete(this); + } + + private void checkNullSupport(K key) { + if (key == null && !nullPointerSupport) + throw new OSBTreeException("Null keys are not supported.", this); + } + + @SuppressWarnings("unchecked") + private boolean put(K key, V value, OIndexEngine.Validator validator) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryUpdateTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during sbtree entrie put", this), e); + } + + acquireExclusiveLock(); + try { + checkNullSupport(key); + + if (key != null) { + final int keySize = keySerializer.getObjectSize(key, (Object[]) keyTypes); + + final int valueSize = valueSerializer.getObjectSize(value); + if (keySize > MAX_KEY_SIZE) + throw new OTooBigIndexKeyException( + "Key size is more than allowed, operation was canceled. Current key size " + keySize + ", allowed " + MAX_KEY_SIZE, + getName()); + + final boolean createLinkToTheValue = valueSize > MAX_EMBEDDED_VALUE_SIZE; + + key = keySerializer.preprocess(key, (Object[]) keyTypes); + long valueLink = -1; + if (createLinkToTheValue) + valueLink = createLinkToTheValue(value, atomicOperation); + + final OSBTreeValue treeValue = new OSBTreeValue(createLinkToTheValue, valueLink, + createLinkToTheValue ? null : value); + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketSearchResult.getLastPathItem(), false); + keyBucketCacheEntry.acquireExclusiveLock(); + OSBTreeBucket keyBucket = new OSBTreeBucket(keyBucketCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, keyBucketCacheEntry)); + + if (validator != null) { + boolean failure = true; // assuming validation throws by default + boolean ignored = false; + + try { + final V oldValue = bucketSearchResult.itemIndex > -1 ? + readValue(keyBucket.getValue(bucketSearchResult.itemIndex), atomicOperation) : + null; + + final Object result = validator.validate(key, oldValue, value); + if (result == OIndexEngine.Validator.IGNORE) { + ignored = true; + failure = false; + return false; + } + + value = (V) result; + failure = false; + } finally { + if (failure || ignored) { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + } + if (ignored) // in case of a failure atomic operation will be ended in a usual way below + endAtomicOperation(false, null); + } + } + + int insertionIndex; + int sizeDiff; + if (bucketSearchResult.itemIndex >= 0) { + int updateResult = keyBucket.updateValue(bucketSearchResult.itemIndex, treeValue); + + if (updateResult >= 0) { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + + endAtomicOperation(false, null); + return true; + } else { + assert updateResult == -1; + + long removedLinkedValue = keyBucket.remove(bucketSearchResult.itemIndex); + if (removedLinkedValue >= 0) + removeLinkedValue(removedLinkedValue, atomicOperation); + + insertionIndex = bucketSearchResult.itemIndex; + sizeDiff = 0; + } + } else { + insertionIndex = -bucketSearchResult.itemIndex - 1; + sizeDiff = 1; + } + + while (!keyBucket.addEntry(insertionIndex, new OSBTreeBucket.SBTreeEntry(-1, -1, key, treeValue), true)) { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + + bucketSearchResult = splitBucket(bucketSearchResult.path, insertionIndex, key, atomicOperation); + + insertionIndex = bucketSearchResult.itemIndex; + + keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketSearchResult.getLastPathItem(), false); + keyBucketCacheEntry.acquireExclusiveLock(); + + keyBucket = new OSBTreeBucket(keyBucketCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, keyBucketCacheEntry)); + } + + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + + if (sizeDiff != 0) + setSize(size() + sizeDiff, atomicOperation); + } else { + OCacheEntry cacheEntry; + boolean isNew = false; + + if (getFilledUpTo(atomicOperation, nullBucketFileId) == 0) { + cacheEntry = addPage(atomicOperation, nullBucketFileId); + isNew = true; + } else + cacheEntry = loadPage(atomicOperation, nullBucketFileId, 0, false); + + final int valueSize = valueSerializer.getObjectSize(value); + final boolean createLinkToTheValue = valueSize > MAX_EMBEDDED_VALUE_SIZE; + + long valueLink = -1; + if (createLinkToTheValue) + valueLink = createLinkToTheValue(value, atomicOperation); + + final OSBTreeValue treeValue = new OSBTreeValue(createLinkToTheValue, valueLink, + createLinkToTheValue ? null : value); + + int sizeDiff = 0; + + boolean ignored = false; + cacheEntry.acquireExclusiveLock(); + try { + final ONullBucket nullBucket = new ONullBucket(cacheEntry, getChanges(atomicOperation, cacheEntry), + valueSerializer, isNew); + final OSBTreeValue oldValue = nullBucket.getValue(); + + if (validator != null) { + final V oldValueValue = oldValue == null ? null : readValue(oldValue, atomicOperation); + + final Object result = validator.validate(null, oldValueValue, value); + if (result == OIndexEngine.Validator.IGNORE) { + ignored = true; + return false; + } + + value = (V) result; + } + + if (oldValue != null) + sizeDiff = -1; + + nullBucket.setValue(treeValue); + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + if (ignored) + endAtomicOperation(false, null); + } + + sizeDiff++; + + setSize(size() + sizeDiff, atomicOperation); + } + + endAtomicOperation(false, null); + return true; + } catch (IOException e) { + rollback(e); + throw OException + .wrapException(new OSBTreeException("Error during index update with key " + key + " and value " + value, this), e); + } catch (RuntimeException e) { + rollback(e); + throw e; + } finally { + releaseExclusiveLock(); + } + } finally { + if (statistic != null) + statistic.stopIndexEntryUpdateTimer(); + completeOperation(); + } + } + + private void removeLinkedValue(long removedLink, OAtomicOperation atomicOperation) throws IOException { + long nextPage = removedLink; + do { + removedLink = nextPage; + + OCacheEntry valueEntry = loadPage(atomicOperation, fileId, removedLink, false); + valueEntry.acquireSharedLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(valueEntry, getChanges(atomicOperation, valueEntry), false); + nextPage = valuePage.getNextPage(); + } finally { + valueEntry.releaseSharedLock(); + releasePage(atomicOperation, valueEntry); + } + + removeValuePage(removedLink, atomicOperation); + } while (nextPage >= 0); + } + + private void removeValuePage(long pageIndex, OAtomicOperation atomicOperation) throws IOException { + long prevFreeListItem; + + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + + rootCacheEntry.acquireExclusiveLock(); + OSBTreeBucket rootBucket = new OSBTreeBucket(rootCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + try { + prevFreeListItem = rootBucket.getValuesFreeListFirstIndex(); + rootBucket.setValuesFreeListFirstIndex(pageIndex); + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + + OCacheEntry valueEntry = loadPage(atomicOperation, fileId, pageIndex, false); + valueEntry.acquireExclusiveLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(valueEntry, getChanges(atomicOperation, valueEntry), false); + valuePage.setNextFreeListPage(prevFreeListItem); + } finally { + valueEntry.releaseExclusiveLock(); + releasePage(atomicOperation, valueEntry); + } + } + + private long createLinkToTheValue(V value, OAtomicOperation atomicOperation) throws IOException { + byte[] serializeValue = new byte[valueSerializer.getObjectSize(value)]; + valueSerializer.serializeNativeObject(value, serializeValue, 0); + + final int amountOfPages = OSBTreeValuePage.calculateAmountOfPage(serializeValue.length); + + int position = 0; + long freeListPageIndex = allocateValuePageFromFreeList(atomicOperation); + + OCacheEntry cacheEntry; + if (freeListPageIndex < 0) + cacheEntry = addPage(atomicOperation, fileId); + else + cacheEntry = loadPage(atomicOperation, fileId, freeListPageIndex, false); + + final long valueLink = cacheEntry.getPageIndex(); + cacheEntry.acquireExclusiveLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(cacheEntry, getChanges(atomicOperation, cacheEntry), + freeListPageIndex >= 0); + position = valuePage.fillBinaryContent(serializeValue, position); + + valuePage.setNextFreeListPage(-1); + valuePage.setNextPage(-1); + + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + long prevPage = valueLink; + for (int i = 1; i < amountOfPages; i++) { + freeListPageIndex = allocateValuePageFromFreeList(atomicOperation); + + if (freeListPageIndex < 0) + cacheEntry = addPage(atomicOperation, fileId); + else + cacheEntry = loadPage(atomicOperation, fileId, freeListPageIndex, false); + + cacheEntry.acquireExclusiveLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(cacheEntry, getChanges(atomicOperation, cacheEntry), + freeListPageIndex >= 0); + position = valuePage.fillBinaryContent(serializeValue, position); + + valuePage.setNextFreeListPage(-1); + valuePage.setNextPage(-1); + + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + OCacheEntry prevPageCacheEntry = loadPage(atomicOperation, fileId, prevPage, false); + prevPageCacheEntry.acquireExclusiveLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(prevPageCacheEntry, getChanges(atomicOperation, prevPageCacheEntry), + freeListPageIndex >= 0); + valuePage.setNextPage(cacheEntry.getPageIndex()); + + } finally { + prevPageCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, prevPageCacheEntry); + } + + prevPage = cacheEntry.getPageIndex(); + } + + return valueLink; + } + + private long allocateValuePageFromFreeList(OAtomicOperation atomicOperation) throws IOException { + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + assert rootCacheEntry != null; + + rootCacheEntry.acquireSharedLock(); + long freeListFirstIndex; + OSBTreeBucket rootBucket; + try { + rootBucket = new OSBTreeBucket(rootCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + freeListFirstIndex = rootBucket.getValuesFreeListFirstIndex(); + } finally { + rootCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, rootCacheEntry); + } + + if (freeListFirstIndex >= 0) { + OCacheEntry freePageEntry = loadPage(atomicOperation, fileId, freeListFirstIndex, false); + freePageEntry.acquireExclusiveLock(); + try { + OSBTreeValuePage valuePage = new OSBTreeValuePage(freePageEntry, getChanges(atomicOperation, freePageEntry), false); + long nextFreeListIndex = valuePage.getNextFreeListPage(); + + rootCacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + rootCacheEntry.acquireExclusiveLock(); + rootBucket = new OSBTreeBucket(rootCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + try { + rootBucket.setValuesFreeListFirstIndex(nextFreeListIndex); + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + + valuePage.setNextFreeListPage(-1); + } finally { + freePageEntry.releaseExclusiveLock(); + releasePage(atomicOperation, freePageEntry); + } + + return freePageEntry.getPageIndex(); + } + + return -1; + } + + private void rollback(Exception e) { + try { + endAtomicOperation(true, e); + } catch (IOException e1) { + OLogManager.instance().error(this, "Error during sbtree operation rollback", e1); + } + } + + private void setSize(long size, OAtomicOperation atomicOperation) throws IOException { + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, ROOT_INDEX, false); + rootCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBucket rootBucket = new OSBTreeBucket(rootCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, rootCacheEntry)); + rootBucket.setTreeSize(size); + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + } + + private OSBTreeCursor iterateEntriesMinorDesc(K key, boolean inclusive, OAtomicOperation atomicOperation) + throws IOException { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + key = enhanceCompositeKeyMinorDesc(key, inclusive); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + + long pageIndex = bucketSearchResult.getLastPathItem(); + int index; + if (bucketSearchResult.itemIndex >= 0) { + index = inclusive ? bucketSearchResult.itemIndex : bucketSearchResult.itemIndex - 1; + } else { + index = -bucketSearchResult.itemIndex - 2; + } + + return new OSBTreeCursorBackward(pageIndex, index, null, key, false, inclusive); + } + + private OSBTreeCursor iterateEntriesMinorAsc(K key, boolean inclusive, OAtomicOperation atomicOperation) + throws IOException { + acquireSharedLock(); + try { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + key = enhanceCompositeKeyMinorAsc(key, inclusive); + + final BucketSearchResult searchResult; + searchResult = firstItem(atomicOperation); + if (searchResult == null) + return new OSBTreeCursor() { + @Override + public Map.Entry next(int prefetchSize) { + return null; + } + }; + + return new OSBTreeCursorForward(searchResult.getLastPathItem(), searchResult.itemIndex, null, key, false, inclusive); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during finding first key in sbtree [" + getName() + "]", this), e); + } finally { + releaseSharedLock(); + } + + } + + private K enhanceCompositeKeyMinorDesc(K key, boolean inclusive) { + final PartialSearchMode partialSearchMode; + if (inclusive) + partialSearchMode = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchMode = PartialSearchMode.LOWEST_BOUNDARY; + + key = enhanceCompositeKey(key, partialSearchMode); + return key; + } + + private K enhanceCompositeKeyMinorAsc(K key, boolean inclusive) { + final PartialSearchMode partialSearchMode; + if (inclusive) + partialSearchMode = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchMode = PartialSearchMode.LOWEST_BOUNDARY; + + key = enhanceCompositeKey(key, partialSearchMode); + return key; + } + + private OSBTreeCursor iterateEntriesMajorAsc(K key, boolean inclusive, OAtomicOperation atomicOperation) + throws IOException { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + key = enhanceCompositeKeyMajorAsc(key, inclusive); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + + long pageIndex = bucketSearchResult.getLastPathItem(); + int index; + if (bucketSearchResult.itemIndex >= 0) { + index = inclusive ? bucketSearchResult.itemIndex : bucketSearchResult.itemIndex + 1; + } else { + index = -bucketSearchResult.itemIndex - 1; + } + + return new OSBTreeCursorForward(pageIndex, index, key, null, inclusive, false); + } + + private OSBTreeCursor iterateEntriesMajorDesc(K key, boolean inclusive, OAtomicOperation atomicOperation) + throws IOException { + final BucketSearchResult searchResult; + acquireSharedLock(); + try { + key = keySerializer.preprocess(key, (Object[]) keyTypes); + key = enhanceCompositeKeyMajorDesc(key, inclusive); + + searchResult = lastItem(atomicOperation); + if (searchResult == null) + return new OSBTreeCursor() { + @Override + public Map.Entry next(int prefetchSize) { + return null; + } + }; + + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during finding last key in sbtree [" + getName() + "]", this), e); + } finally { + releaseSharedLock(); + } + + return new OSBTreeCursorBackward(searchResult.getLastPathItem(), searchResult.itemIndex, key, null, inclusive, false); + } + + private K enhanceCompositeKeyMajorAsc(K key, boolean inclusive) { + final PartialSearchMode partialSearchMode; + if (inclusive) + partialSearchMode = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchMode = PartialSearchMode.HIGHEST_BOUNDARY; + + key = enhanceCompositeKey(key, partialSearchMode); + return key; + } + + private K enhanceCompositeKeyMajorDesc(K key, boolean inclusive) { + final PartialSearchMode partialSearchMode; + if (inclusive) + partialSearchMode = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchMode = PartialSearchMode.HIGHEST_BOUNDARY; + + key = enhanceCompositeKey(key, partialSearchMode); + return key; + } + + private BucketSearchResult firstItem(OAtomicOperation atomicOperation) throws IOException { + LinkedList path = new LinkedList(); + + long bucketIndex = ROOT_INDEX; + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketIndex, false); + int itemIndex = 0; + cacheEntry.acquireSharedLock(); + try { + OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + while (true) { + if (!bucket.isLeaf()) { + if (bucket.isEmpty() || itemIndex > bucket.size()) { + if (!path.isEmpty()) { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketIndex = pagePathItemUnit.pageIndex; + itemIndex = pagePathItemUnit.itemIndex + 1; + } else + return null; + } else { + path.add(new PagePathItemUnit(bucketIndex, itemIndex)); + + if (itemIndex < bucket.size()) { + OSBTreeBucket.SBTreeEntry entry = bucket.getEntry(itemIndex); + bucketIndex = entry.leftChild; + } else { + OSBTreeBucket.SBTreeEntry entry = bucket.getEntry(itemIndex - 1); + bucketIndex = entry.rightChild; + } + + itemIndex = 0; + } + } else { + if (bucket.isEmpty()) { + if (!path.isEmpty()) { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketIndex = pagePathItemUnit.pageIndex; + itemIndex = pagePathItemUnit.itemIndex + 1; + } else + return null; + } else { + final ArrayList resultPath = new ArrayList(path.size() + 1); + for (PagePathItemUnit pathItemUnit : path) + resultPath.add(pathItemUnit.pageIndex); + + resultPath.add(bucketIndex); + return new BucketSearchResult(0, resultPath); + } + } + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + cacheEntry = loadPage(atomicOperation, fileId, bucketIndex, false); + cacheEntry.acquireSharedLock(); + + bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + } + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + private BucketSearchResult lastItem(OAtomicOperation atomicOperation) throws IOException { + LinkedList path = new LinkedList(); + + long bucketIndex = ROOT_INDEX; + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketIndex, false); + cacheEntry.acquireSharedLock(); + + OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + int itemIndex = bucket.size() - 1; + try { + while (true) { + if (!bucket.isLeaf()) { + if (itemIndex < -1) { + if (!path.isEmpty()) { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketIndex = pagePathItemUnit.pageIndex; + itemIndex = pagePathItemUnit.itemIndex - 1; + } else + return null; + } else { + path.add(new PagePathItemUnit(bucketIndex, itemIndex)); + + if (itemIndex > -1) { + OSBTreeBucket.SBTreeEntry entry = bucket.getEntry(itemIndex); + bucketIndex = entry.rightChild; + } else { + OSBTreeBucket.SBTreeEntry entry = bucket.getEntry(0); + bucketIndex = entry.leftChild; + } + + itemIndex = OSBTreeBucket.MAX_PAGE_SIZE_BYTES + 1; + } + } else { + if (bucket.isEmpty()) { + if (!path.isEmpty()) { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketIndex = pagePathItemUnit.pageIndex; + itemIndex = pagePathItemUnit.itemIndex - 1; + } else + return null; + } else { + final ArrayList resultPath = new ArrayList(path.size() + 1); + for (PagePathItemUnit pathItemUnit : path) + resultPath.add(pathItemUnit.pageIndex); + + resultPath.add(bucketIndex); + + return new BucketSearchResult(bucket.size() - 1, resultPath); + } + } + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + cacheEntry = loadPage(atomicOperation, fileId, bucketIndex, false); + cacheEntry.acquireSharedLock(); + + bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + if (itemIndex == OSBTreeBucket.MAX_PAGE_SIZE_BYTES + 1) + itemIndex = bucket.size() - 1; + } + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + private OSBTreeCursor iterateEntriesBetweenAscOrder(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, + OAtomicOperation atomicOperation) throws IOException { + keyFrom = keySerializer.preprocess(keyFrom, (Object[]) keyTypes); + keyTo = keySerializer.preprocess(keyTo, (Object[]) keyTypes); + + keyFrom = enhanceFromCompositeKeyBetweenAsc(keyFrom, fromInclusive); + keyTo = enhanceToCompositeKeyBetweenAsc(keyTo, toInclusive); + + BucketSearchResult bucketSearchResultFrom = findBucket(keyFrom, atomicOperation); + + long pageIndexFrom = bucketSearchResultFrom.getLastPathItem(); + + int indexFrom; + if (bucketSearchResultFrom.itemIndex >= 0) { + indexFrom = fromInclusive ? bucketSearchResultFrom.itemIndex : bucketSearchResultFrom.itemIndex + 1; + } else { + indexFrom = -bucketSearchResultFrom.itemIndex - 1; + } + + return new OSBTreeCursorForward(pageIndexFrom, indexFrom, keyFrom, keyTo, fromInclusive, toInclusive); + } + + private OSBTreeCursor iterateEntriesBetweenDescOrder(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, + OAtomicOperation atomicOperation) throws IOException { + keyFrom = keySerializer.preprocess(keyFrom, (Object[]) keyTypes); + keyTo = keySerializer.preprocess(keyTo, (Object[]) keyTypes); + + keyFrom = enhanceFromCompositeKeyBetweenDesc(keyFrom, fromInclusive); + keyTo = enhanceToCompositeKeyBetweenDesc(keyTo, toInclusive); + + BucketSearchResult bucketSearchResultTo = findBucket(keyTo, atomicOperation); + + long pageIndexTo = bucketSearchResultTo.getLastPathItem(); + + int indexTo; + if (bucketSearchResultTo.itemIndex >= 0) { + indexTo = toInclusive ? bucketSearchResultTo.itemIndex : bucketSearchResultTo.itemIndex - 1; + } else { + indexTo = -bucketSearchResultTo.itemIndex - 2; + } + + return new OSBTreeCursorBackward(pageIndexTo, indexTo, keyFrom, keyTo, fromInclusive, toInclusive); + } + + private K enhanceToCompositeKeyBetweenAsc(K keyTo, boolean toInclusive) { + PartialSearchMode partialSearchModeTo; + if (toInclusive) + partialSearchModeTo = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchModeTo = PartialSearchMode.LOWEST_BOUNDARY; + + keyTo = enhanceCompositeKey(keyTo, partialSearchModeTo); + return keyTo; + } + + private K enhanceFromCompositeKeyBetweenAsc(K keyFrom, boolean fromInclusive) { + PartialSearchMode partialSearchModeFrom; + if (fromInclusive) + partialSearchModeFrom = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchModeFrom = PartialSearchMode.HIGHEST_BOUNDARY; + + keyFrom = enhanceCompositeKey(keyFrom, partialSearchModeFrom); + return keyFrom; + } + + private K enhanceToCompositeKeyBetweenDesc(K keyTo, boolean toInclusive) { + PartialSearchMode partialSearchModeTo; + if (toInclusive) + partialSearchModeTo = PartialSearchMode.HIGHEST_BOUNDARY; + else + partialSearchModeTo = PartialSearchMode.LOWEST_BOUNDARY; + + keyTo = enhanceCompositeKey(keyTo, partialSearchModeTo); + return keyTo; + } + + private K enhanceFromCompositeKeyBetweenDesc(K keyFrom, boolean fromInclusive) { + PartialSearchMode partialSearchModeFrom; + if (fromInclusive) + partialSearchModeFrom = PartialSearchMode.LOWEST_BOUNDARY; + else + partialSearchModeFrom = PartialSearchMode.HIGHEST_BOUNDARY; + + keyFrom = enhanceCompositeKey(keyFrom, partialSearchModeFrom); + return keyFrom; + } + + private BucketSearchResult splitBucket(List path, int keyIndex, K keyToInsert, OAtomicOperation atomicOperation) + throws IOException { + long pageIndex = path.get(path.size() - 1); + + OCacheEntry bucketEntry = loadPage(atomicOperation, fileId, pageIndex, false); + + bucketEntry.acquireExclusiveLock(); + try { + OSBTreeBucket bucketToSplit = new OSBTreeBucket(bucketEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, bucketEntry)); + + final boolean splitLeaf = bucketToSplit.isLeaf(); + final int bucketSize = bucketToSplit.size(); + + int indexToSplit = bucketSize >>> 1; + final K separationKey = bucketToSplit.getKey(indexToSplit); + final List> rightEntries = new ArrayList>(indexToSplit); + + final int startRightIndex = splitLeaf ? indexToSplit : indexToSplit + 1; + + for (int i = startRightIndex; i < bucketSize; i++) + rightEntries.add(bucketToSplit.getEntry(i)); + + if (pageIndex != ROOT_INDEX) { + return splitNonRootBucket(path, keyIndex, keyToInsert, pageIndex, bucketToSplit, splitLeaf, indexToSplit, separationKey, + rightEntries, atomicOperation); + } else { + return splitRootBucket(path, keyIndex, keyToInsert, pageIndex, bucketEntry, bucketToSplit, splitLeaf, indexToSplit, + separationKey, rightEntries, atomicOperation); + } + } finally { + bucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, bucketEntry); + } + } + + private BucketSearchResult splitNonRootBucket(List path, int keyIndex, K keyToInsert, long pageIndex, + OSBTreeBucket bucketToSplit, boolean splitLeaf, int indexToSplit, K separationKey, + List> rightEntries, OAtomicOperation atomicOperation) throws IOException { + OCacheEntry rightBucketEntry = addPage(atomicOperation, fileId); + rightBucketEntry.acquireExclusiveLock(); + + try { + OSBTreeBucket newRightBucket = new OSBTreeBucket(rightBucketEntry, splitLeaf, keySerializer, keyTypes, + valueSerializer, getChanges(atomicOperation, rightBucketEntry)); + newRightBucket.addAll(rightEntries); + + bucketToSplit.shrink(indexToSplit); + + if (splitLeaf) { + long rightSiblingPageIndex = bucketToSplit.getRightSibling(); + + newRightBucket.setRightSibling(rightSiblingPageIndex); + newRightBucket.setLeftSibling(pageIndex); + + bucketToSplit.setRightSibling(rightBucketEntry.getPageIndex()); + + if (rightSiblingPageIndex >= 0) { + final OCacheEntry rightSiblingBucketEntry = loadPage(atomicOperation, fileId, rightSiblingPageIndex, false); + rightSiblingBucketEntry.acquireExclusiveLock(); + OSBTreeBucket rightSiblingBucket = new OSBTreeBucket(rightSiblingBucketEntry, keySerializer, keyTypes, + valueSerializer, getChanges(atomicOperation, rightSiblingBucketEntry)); + try { + rightSiblingBucket.setLeftSibling(rightBucketEntry.getPageIndex()); + } finally { + rightSiblingBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightSiblingBucketEntry); + } + } + } + + long parentIndex = path.get(path.size() - 2); + OCacheEntry parentCacheEntry = loadPage(atomicOperation, fileId, parentIndex, false); + parentCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBucket parentBucket = new OSBTreeBucket(parentCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, parentCacheEntry)); + OSBTreeBucket.SBTreeEntry parentEntry = new OSBTreeBucket.SBTreeEntry(pageIndex, + rightBucketEntry.getPageIndex(), separationKey, null); + + int insertionIndex = parentBucket.find(separationKey); + assert insertionIndex < 0; + + insertionIndex = -insertionIndex - 1; + while (!parentBucket.addEntry(insertionIndex, parentEntry, true)) { + parentCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, parentCacheEntry); + + BucketSearchResult bucketSearchResult = splitBucket(path.subList(0, path.size() - 1), insertionIndex, separationKey, + atomicOperation); + + parentIndex = bucketSearchResult.getLastPathItem(); + parentCacheEntry = loadPage(atomicOperation, fileId, parentIndex, false); + parentCacheEntry.acquireExclusiveLock(); + + insertionIndex = bucketSearchResult.itemIndex; + + parentBucket = new OSBTreeBucket(parentCacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, parentCacheEntry)); + } + + } finally { + parentCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, parentCacheEntry); + } + + } finally { + rightBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightBucketEntry); + } + + ArrayList resultPath = new ArrayList(path.subList(0, path.size() - 1)); + + if (comparator.compare(keyToInsert, separationKey) < 0) { + resultPath.add(pageIndex); + return new BucketSearchResult(keyIndex, resultPath); + } + + resultPath.add(rightBucketEntry.getPageIndex()); + if (splitLeaf) { + return new BucketSearchResult(keyIndex - indexToSplit, resultPath); + } + + resultPath.add(rightBucketEntry.getPageIndex()); + return new BucketSearchResult(keyIndex - indexToSplit - 1, resultPath); + } + + private BucketSearchResult splitRootBucket(List path, int keyIndex, K keyToInsert, long pageIndex, OCacheEntry bucketEntry, + OSBTreeBucket bucketToSplit, boolean splitLeaf, int indexToSplit, K separationKey, + List> rightEntries, OAtomicOperation atomicOperation) throws IOException { + final long freeListPage = bucketToSplit.getValuesFreeListFirstIndex(); + final long treeSize = bucketToSplit.getTreeSize(); + + final List> leftEntries = new ArrayList>(indexToSplit); + + for (int i = 0; i < indexToSplit; i++) + leftEntries.add(bucketToSplit.getEntry(i)); + + OCacheEntry leftBucketEntry = addPage(atomicOperation, fileId); + + OCacheEntry rightBucketEntry = addPage(atomicOperation, fileId); + leftBucketEntry.acquireExclusiveLock(); + try { + OSBTreeBucket newLeftBucket = new OSBTreeBucket(leftBucketEntry, splitLeaf, keySerializer, keyTypes, + valueSerializer, getChanges(atomicOperation, leftBucketEntry)); + newLeftBucket.addAll(leftEntries); + + if (splitLeaf) + newLeftBucket.setRightSibling(rightBucketEntry.getPageIndex()); + + } finally { + leftBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, leftBucketEntry); + } + + rightBucketEntry.acquireExclusiveLock(); + try { + OSBTreeBucket newRightBucket = new OSBTreeBucket(rightBucketEntry, splitLeaf, keySerializer, keyTypes, + valueSerializer, getChanges(atomicOperation, rightBucketEntry)); + newRightBucket.addAll(rightEntries); + + if (splitLeaf) + newRightBucket.setLeftSibling(leftBucketEntry.getPageIndex()); + } finally { + rightBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightBucketEntry); + } + + bucketToSplit = new OSBTreeBucket(bucketEntry, false, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, bucketEntry)); + + bucketToSplit.setTreeSize(treeSize); + bucketToSplit.setValuesFreeListFirstIndex(freeListPage); + + bucketToSplit.addEntry(0, + new OSBTreeBucket.SBTreeEntry(leftBucketEntry.getPageIndex(), rightBucketEntry.getPageIndex(), separationKey, null), + true); + + ArrayList resultPath = new ArrayList(path.subList(0, path.size() - 1)); + + if (comparator.compare(keyToInsert, separationKey) < 0) { + resultPath.add(leftBucketEntry.getPageIndex()); + return new BucketSearchResult(keyIndex, resultPath); + } + + resultPath.add(rightBucketEntry.getPageIndex()); + + if (splitLeaf) + return new BucketSearchResult(keyIndex - indexToSplit, resultPath); + + return new BucketSearchResult(keyIndex - indexToSplit - 1, resultPath); + } + + private BucketSearchResult findBucket(K key, OAtomicOperation atomicOperation) throws IOException { + long pageIndex = ROOT_INDEX; + final ArrayList path = new ArrayList(); + + while (true) { + if (path.size() > MAX_PATH_LENGTH) + throw new OSBTreeException( + "We reached max level of depth of SBTree but still found nothing, seems like tree is in corrupted state. You should rebuild index related to given query.", + this); + + path.add(pageIndex); + final OCacheEntry bucketEntry = loadPage(atomicOperation, fileId, pageIndex, false); + bucketEntry.acquireSharedLock(); + final OSBTreeBucket.SBTreeEntry entry; + try { + final OSBTreeBucket keyBucket = new OSBTreeBucket(bucketEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, bucketEntry)); + final int index = keyBucket.find(key); + + if (keyBucket.isLeaf()) + return new BucketSearchResult(index, path); + + if (index >= 0) + entry = keyBucket.getEntry(index); + else { + final int insertionIndex = -index - 1; + if (insertionIndex >= keyBucket.size()) + entry = keyBucket.getEntry(insertionIndex - 1); + else + entry = keyBucket.getEntry(insertionIndex); + } + + } finally { + bucketEntry.releaseSharedLock(); + releasePage(atomicOperation, bucketEntry); + } + + if (comparator.compare(key, entry.key) >= 0) + pageIndex = entry.rightChild; + else + pageIndex = entry.leftChild; + } + } + + private K enhanceCompositeKey(K key, PartialSearchMode partialSearchMode) { + if (!(key instanceof OCompositeKey)) + return key; + + final OCompositeKey compositeKey = (OCompositeKey) key; + + if (!(keySize == 1 || compositeKey.getKeys().size() == keySize || partialSearchMode.equals(PartialSearchMode.NONE))) { + final OCompositeKey fullKey = new OCompositeKey(compositeKey); + int itemsToAdd = keySize - fullKey.getKeys().size(); + + final Comparable keyItem; + if (partialSearchMode.equals(PartialSearchMode.HIGHEST_BOUNDARY)) + keyItem = ALWAYS_GREATER_KEY; + else + keyItem = ALWAYS_LESS_KEY; + + for (int i = 0; i < itemsToAdd; i++) + fullKey.addKey(keyItem); + + return (K) fullKey; + } + + return key; + } + + private V readValue(OSBTreeValue sbTreeValue, OAtomicOperation atomicOperation) throws IOException { + if (!sbTreeValue.isLink()) + return sbTreeValue.getValue(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, sbTreeValue.getLink(), false); + cacheEntry.acquireSharedLock(); + + OSBTreeValuePage valuePage = new OSBTreeValuePage(cacheEntry, getChanges(atomicOperation, cacheEntry), false); + + int totalSize = valuePage.getSize(); + int currentSize = 0; + byte[] value = new byte[totalSize]; + + while (currentSize < totalSize) { + currentSize = valuePage.readBinaryContent(value, currentSize); + + long nextPage = valuePage.getNextPage(); + if (nextPage >= 0) { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + cacheEntry = loadPage(atomicOperation, fileId, nextPage, false); + cacheEntry.acquireSharedLock(); + + valuePage = new OSBTreeValuePage(cacheEntry, getChanges(atomicOperation, cacheEntry), false); + } + } + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + return valueSerializer.deserializeNativeObject(value, 0); + } + + private Map.Entry convertToMapEntry(OSBTreeBucket.SBTreeEntry treeEntry, OAtomicOperation atomicOperation) + throws IOException { + final K key = treeEntry.key; + final V value = readValue(treeEntry.value, atomicOperation); + + return new Map.Entry() { + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + + /** + * Indicates search behavior in case of {@link OCompositeKey} keys that have less amount of internal keys are used, whether + * lowest or highest partially matched key should be used. + */ + private static enum PartialSearchMode { + /** + * Any partially matched key will be used as search result. + */ + NONE, /** + * The biggest partially matched key will be used as search result. + */ + HIGHEST_BOUNDARY, + + /** + * The smallest partially matched key will be used as search result. + */ + LOWEST_BOUNDARY + } + + public interface OSBTreeCursor { + Map.Entry next(int prefetchSize); + } + + public interface OSBTreeKeyCursor { + K next(int prefetchSize); + } + + private static class BucketSearchResult { + private final int itemIndex; + private final ArrayList path; + + private BucketSearchResult(int itemIndex, ArrayList path) { + this.itemIndex = itemIndex; + this.path = path; + } + + public long getLastPathItem() { + return path.get(path.size() - 1); + } + } + + private static final class PagePathItemUnit { + private final long pageIndex; + private final int itemIndex; + + private PagePathItemUnit(long pageIndex, int itemIndex) { + this.pageIndex = pageIndex; + this.itemIndex = itemIndex; + } + } + + public class OSBTreeFullKeyCursor implements OSBTreeKeyCursor { + private long pageIndex; + private int itemIndex; + + private List keysCache = new ArrayList(); + private Iterator keysIterator = new OEmptyIterator(); + + public OSBTreeFullKeyCursor(long startPageIndex) { + pageIndex = startPageIndex; + itemIndex = 0; + } + + @Override + public K next(int prefetchSize) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + if (keysIterator == null) + return null; + + if (keysIterator.hasNext()) + return keysIterator.next(); + + keysCache.clear(); + + if (prefetchSize < 0 || prefetchSize > OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger()) + prefetchSize = OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger(); + + if (prefetchSize == 0) + prefetchSize = 1; + + atomicOperationsManager.acquireReadLock(OSBTree.this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + while (keysCache.size() < prefetchSize) { + if (pageIndex == -1) + break; + + if (pageIndex >= getFilledUpTo(atomicOperation, fileId)) { + pageIndex = -1; + break; + } + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + final OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + if (itemIndex >= bucket.size()) { + pageIndex = bucket.getRightSibling(); + itemIndex = 0; + continue; + } + + final Map.Entry entry = convertToMapEntry(bucket.getEntry(itemIndex), atomicOperation); + itemIndex++; + + keysCache.add(entry.getKey()); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during element iteration", OSBTree.this), e); + } finally { + atomicOperationsManager.releaseReadLock(OSBTree.this); + } + + if (keysCache.isEmpty()) { + keysCache = null; + return null; + } + + keysIterator = keysCache.iterator(); + return keysIterator.next(); + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + } + + private final class OSBTreeCursorForward implements OSBTreeCursor { + private final K fromKey; + private final K toKey; + private final boolean fromKeyInclusive; + private final boolean toKeyInclusive; + + private long pageIndex; + private int itemIndex; + + private List> dataCache = new ArrayList>(); + private Iterator> dataCacheIterator = OEmptyMapEntryIterator.INSTANCE; + + private OSBTreeCursorForward(long startPageIndex, int startItemIndex, K fromKey, K toKey, boolean fromKeyInclusive, + boolean toKeyInclusive) { + this.fromKey = fromKey; + this.toKey = toKey; + this.fromKeyInclusive = fromKeyInclusive; + this.toKeyInclusive = toKeyInclusive; + + pageIndex = startPageIndex; + itemIndex = startItemIndex; + } + + public Map.Entry next(int prefetchSize) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + if (dataCacheIterator == null) + return null; + + if (dataCacheIterator.hasNext()) + return dataCacheIterator.next(); + + dataCache.clear(); + + if (prefetchSize < 0 || prefetchSize > OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger()) + prefetchSize = OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger(); + + if (prefetchSize == 0) + prefetchSize = 1; + + atomicOperationsManager.acquireReadLock(OSBTree.this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + while (dataCache.size() < prefetchSize) { + if (pageIndex == -1) + break; + + if (pageIndex >= getFilledUpTo(atomicOperation, fileId)) { + pageIndex = -1; + break; + } + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + final OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + if (itemIndex >= bucket.size()) { + pageIndex = bucket.getRightSibling(); + itemIndex = 0; + continue; + } + + final Map.Entry entry = convertToMapEntry(bucket.getEntry(itemIndex), atomicOperation); + itemIndex++; + + if (fromKey != null && (fromKeyInclusive ? + comparator.compare(entry.getKey(), fromKey) < 0 : + comparator.compare(entry.getKey(), fromKey) <= 0)) + continue; + + if (toKey != null && (toKeyInclusive ? + comparator.compare(entry.getKey(), toKey) > 0 : + comparator.compare(entry.getKey(), toKey) >= 0)) { + pageIndex = -1; + break; + } + + dataCache.add(entry); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during element iteration", OSBTree.this), e); + } finally { + atomicOperationsManager.releaseReadLock(OSBTree.this); + } + + if (dataCache.isEmpty()) { + dataCacheIterator = null; + return null; + } + + dataCacheIterator = dataCache.iterator(); + + return dataCacheIterator.next(); + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + } + + private final class OSBTreeCursorBackward implements OSBTreeCursor { + private final K fromKey; + private final K toKey; + private final boolean fromKeyInclusive; + private final boolean toKeyInclusive; + + private long pageIndex; + private int itemIndex; + + private List> dataCache = new ArrayList>(); + private Iterator> dataCacheIterator = OEmptyMapEntryIterator.INSTANCE; + + private OSBTreeCursorBackward(long endPageIndex, int endItemIndex, K fromKey, K toKey, boolean fromKeyInclusive, + boolean toKeyInclusive) { + this.fromKey = fromKey; + this.toKey = toKey; + this.fromKeyInclusive = fromKeyInclusive; + this.toKeyInclusive = toKeyInclusive; + + pageIndex = endPageIndex; + itemIndex = endItemIndex; + } + + public Map.Entry next(int prefetchSize) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startIndexEntryReadTimer(); + try { + if (dataCacheIterator == null) + return null; + + if (dataCacheIterator.hasNext()) + return dataCacheIterator.next(); + + dataCache.clear(); + + if (prefetchSize < 0 || prefetchSize > OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger()) + prefetchSize = OGlobalConfiguration.INDEX_CURSOR_PREFETCH_SIZE.getValueAsInteger(); + + atomicOperationsManager.acquireReadLock(OSBTree.this); + try { + acquireSharedLock(); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + while (dataCache.size() < prefetchSize) { + if (pageIndex >= getFilledUpTo(atomicOperation, fileId)) + pageIndex = getFilledUpTo(atomicOperation, fileId) - 1; + + if (pageIndex == -1) + break; + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, pageIndex, false); + cacheEntry.acquireSharedLock(); + try { + final OSBTreeBucket bucket = new OSBTreeBucket(cacheEntry, keySerializer, keyTypes, valueSerializer, + getChanges(atomicOperation, cacheEntry)); + + if (itemIndex >= bucket.size()) + itemIndex = bucket.size() - 1; + + if (itemIndex < 0) { + pageIndex = bucket.getLeftSibling(); + itemIndex = Integer.MAX_VALUE; + continue; + } + + final Map.Entry entry = convertToMapEntry(bucket.getEntry(itemIndex), atomicOperation); + itemIndex--; + + if (toKey != null && (toKeyInclusive ? + comparator.compare(entry.getKey(), toKey) > 0 : + comparator.compare(entry.getKey(), toKey) >= 0)) + continue; + + if (fromKey != null && (fromKeyInclusive ? + comparator.compare(entry.getKey(), fromKey) < 0 : + comparator.compare(entry.getKey(), fromKey) <= 0)) { + pageIndex = -1; + break; + } + + dataCache.add(entry); + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } + } finally { + releaseSharedLock(); + } + } catch (IOException e) { + throw OException.wrapException(new OSBTreeException("Error during element iteration", OSBTree.this), e); + } finally { + atomicOperationsManager.releaseReadLock(OSBTree.this); + } + + if (dataCache.isEmpty()) { + dataCacheIterator = null; + return null; + } + + dataCacheIterator = dataCache.iterator(); + + return dataCacheIterator.next(); + } finally { + if (statistic != null) + statistic.stopIndexEntryReadTimer(); + completeOperation(); + } + } + } + + @Override + protected void startOperation() { + OSessionStoragePerformanceStatistic sessionStoragePerformanceStatistic = performanceStatisticManager + .getSessionPerformanceStatistic(); + if (sessionStoragePerformanceStatistic != null) { + sessionStoragePerformanceStatistic + .startComponentOperation(getFullName(), OSessionStoragePerformanceStatistic.ComponentType.INDEX); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeBucket.java new file mode 100755 index 00000000000..32a971cfaa5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeBucket.java @@ -0,0 +1,484 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @author Andrey Lomakin + * @since 8/7/13 + */ +public class OSBTreeBucket extends ODurablePage { + private static final int FREE_POINTER_OFFSET = NEXT_FREE_POSITION; + private static final int SIZE_OFFSET = FREE_POINTER_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int IS_LEAF_OFFSET = SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int LEFT_SIBLING_OFFSET = IS_LEAF_OFFSET + OByteSerializer.BYTE_SIZE; + private static final int RIGHT_SIBLING_OFFSET = LEFT_SIBLING_OFFSET + OLongSerializer.LONG_SIZE; + + private static final int TREE_SIZE_OFFSET = RIGHT_SIBLING_OFFSET + OLongSerializer.LONG_SIZE; + + /** + * KEY_SERIALIZER_OFFSET and VALUE_SERIALIZER_OFFSET are no longer used by sb-tree since 1.7. + * + * However we left them in buckets to support backward compatibility. + */ + private static final int KEY_SERIALIZER_OFFSET = TREE_SIZE_OFFSET + OLongSerializer.LONG_SIZE; + private static final int VALUE_SERIALIZER_OFFSET = KEY_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; + + private static final int FREE_VALUES_LIST_OFFSET = VALUE_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; + + private static final int POSITIONS_ARRAY_OFFSET = FREE_VALUES_LIST_OFFSET + OLongSerializer.LONG_SIZE; + + private final boolean isLeaf; + + private final OBinarySerializer keySerializer; + private final OBinarySerializer valueSerializer; + + private final OType[] keyTypes; + + private final Comparator comparator = ODefaultComparator.INSTANCE; + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public OSBTreeBucket(OCacheEntry cacheEntry, boolean isLeaf, OBinarySerializer keySerializer, OType[] keyTypes, + OBinarySerializer valueSerializer, OWALChanges changes) throws IOException { + super(cacheEntry, changes); + + this.isLeaf = isLeaf; + this.keySerializer = keySerializer; + this.keyTypes = keyTypes; + this.valueSerializer = valueSerializer; + + setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES); + setIntValue(SIZE_OFFSET, 0); + + setByteValue(IS_LEAF_OFFSET, (byte) (isLeaf ? 1 : 0)); + setLongValue(LEFT_SIBLING_OFFSET, -1); + setLongValue(RIGHT_SIBLING_OFFSET, -1); + + setLongValue(TREE_SIZE_OFFSET, 0); + setLongValue(FREE_VALUES_LIST_OFFSET, -1); + + setByteValue(KEY_SERIALIZER_OFFSET, this.keySerializer.getId()); + setByteValue(VALUE_SERIALIZER_OFFSET, this.valueSerializer.getId()); + } + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public OSBTreeBucket(OCacheEntry cacheEntry, OBinarySerializer keySerializer, OType[] keyTypes, + OBinarySerializer valueSerializer, OWALChanges changes) { + super(cacheEntry, changes); + this.keyTypes = keyTypes; + + this.isLeaf = getByteValue(IS_LEAF_OFFSET) > 0; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + public void setTreeSize(long size) throws IOException { + setLongValue(TREE_SIZE_OFFSET, size); + } + + public long getTreeSize() { + return getLongValue(TREE_SIZE_OFFSET); + } + + public boolean isEmpty() { + return size() == 0; + } + + public long getValuesFreeListFirstIndex() { + return getLongValue(FREE_VALUES_LIST_OFFSET); + } + + public void setValuesFreeListFirstIndex(long pageIndex) throws IOException { + setLongValue(FREE_VALUES_LIST_OFFSET, pageIndex); + } + + public int find(K key) { + int low = 0; + int high = size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + K midVal = getKey(mid); + int cmp = comparator.compare(midVal, key); + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + public long remove(int entryIndex) throws IOException { + int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE); + int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition); + + int entrySize; + long linkValue = -1; + + if (isLeaf) { + if (valueSerializer.isFixedLength()) { + entrySize = keySize + valueSerializer.getFixedLength() + OByteSerializer.BYTE_SIZE; + } else { + final boolean isLink = getByteValue(entryPosition + keySize) > 0; + + if (!isLink) + entrySize = keySize + getObjectSizeInDirectMemory(valueSerializer, entryPosition + keySize + OByteSerializer.BYTE_SIZE) + + OByteSerializer.BYTE_SIZE; + else { + entrySize = keySize + OByteSerializer.BYTE_SIZE + OLongSerializer.LONG_SIZE; + linkValue = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + keySize + OByteSerializer.BYTE_SIZE); + } + } + } else { + throw new IllegalStateException("Remove is applies to leaf buckets only"); + } + + int size = size(); + if (entryIndex < size - 1) { + moveData(POSITIONS_ARRAY_OFFSET + (entryIndex + 1) * OIntegerSerializer.INT_SIZE, + POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE, (size - entryIndex - 1) * OIntegerSerializer.INT_SIZE); + } + + size--; + setIntValue(SIZE_OFFSET, size); + + int freePointer = getIntValue(FREE_POINTER_OFFSET); + if (size > 0 && entryPosition > freePointer) { + moveData(freePointer, freePointer + entrySize, entryPosition - freePointer); + } + setIntValue(FREE_POINTER_OFFSET, freePointer + entrySize); + + int currentPositionOffset = POSITIONS_ARRAY_OFFSET; + + for (int i = 0; i < size; i++) { + int currentEntryPosition = getIntValue(currentPositionOffset); + if (currentEntryPosition < entryPosition) + setIntValue(currentPositionOffset, currentEntryPosition + entrySize); + currentPositionOffset += OIntegerSerializer.INT_SIZE; + } + + return linkValue; + } + + public int size() { + return getIntValue(SIZE_OFFSET); + } + + public SBTreeEntry getEntry(int entryIndex) { + int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + + if (isLeaf) { + K key = deserializeFromDirectMemory(keySerializer, entryPosition); + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + + boolean isLinkValue = getByteValue(entryPosition) > 0; + long link = -1; + V value = null; + + if (isLinkValue) + link = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + OByteSerializer.BYTE_SIZE); + else + value = deserializeFromDirectMemory(valueSerializer, entryPosition + OByteSerializer.BYTE_SIZE); + + return new SBTreeEntry(-1, -1, key, new OSBTreeValue(link >= 0, link, value)); + } else { + long leftChild = getLongValue(entryPosition); + entryPosition += OLongSerializer.LONG_SIZE; + + long rightChild = getLongValue(entryPosition); + entryPosition += OLongSerializer.LONG_SIZE; + + K key = deserializeFromDirectMemory(keySerializer, entryPosition); + + return new SBTreeEntry(leftChild, rightChild, key, null); + } + } + + /** + * Obtains the value stored under the given entry index in this bucket. + * + * @param entryIndex the value entry index. + * + * @return the obtained value. + */ + public OSBTreeValue getValue(int entryIndex) { + assert isLeaf; + + int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + + // skip key + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + + boolean isLinkValue = getByteValue(entryPosition) > 0; + long link = -1; + V value = null; + + if (isLinkValue) + link = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + OByteSerializer.BYTE_SIZE); + else + value = deserializeFromDirectMemory(valueSerializer, entryPosition + OByteSerializer.BYTE_SIZE); + + return new OSBTreeValue(link >= 0, link, value); + } + + public K getKey(int index) { + int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + + if (!isLeaf) + entryPosition += 2 * OLongSerializer.LONG_SIZE; + + return deserializeFromDirectMemory(keySerializer, entryPosition); + } + + public boolean isLeaf() { + return isLeaf; + } + + public void addAll(List> entries) throws IOException { + for (int i = 0; i < entries.size(); i++) + addEntry(i, entries.get(i), false); + } + + public void shrink(int newSize) throws IOException { + List> treeEntries = new ArrayList>(newSize); + + for (int i = 0; i < newSize; i++) { + treeEntries.add(getEntry(i)); + } + + setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES); + setIntValue(SIZE_OFFSET, 0); + + int index = 0; + for (SBTreeEntry entry : treeEntries) { + addEntry(index, entry, false); + index++; + } + } + + public boolean addEntry(int index, SBTreeEntry treeEntry, boolean updateNeighbors) throws IOException { + final int keySize = keySerializer.getObjectSize(treeEntry.key, (Object[]) keyTypes); + int valueSize = 0; + int entrySize = keySize; + + if (isLeaf) { + if (valueSerializer.isFixedLength()) + valueSize = valueSerializer.getFixedLength(); + else { + if (treeEntry.value.isLink()) + valueSize = OLongSerializer.LONG_SIZE; + else + valueSize = valueSerializer.getObjectSize(treeEntry.value.getValue()); + } + + entrySize += valueSize + OByteSerializer.BYTE_SIZE; + } else + entrySize += 2 * OLongSerializer.LONG_SIZE; + + int size = size(); + int freePointer = getIntValue(FREE_POINTER_OFFSET); + if (freePointer - entrySize < (size + 1) * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) + return false; + + if (index <= size - 1) { + moveData(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, + POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE, (size - index) * OIntegerSerializer.INT_SIZE); + } + + freePointer -= entrySize; + + setIntValue(FREE_POINTER_OFFSET, freePointer); + setIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, freePointer); + setIntValue(SIZE_OFFSET, size + 1); + + if (isLeaf) { + byte[] serializedKey = new byte[keySize]; + keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0, (Object[]) keyTypes); + + freePointer += setBinaryValue(freePointer, serializedKey); + freePointer += setByteValue(freePointer, treeEntry.value.isLink() ? (byte) 1 : (byte) 0); + + byte[] serializedValue = new byte[valueSize]; + if (treeEntry.value.isLink()) + OLongSerializer.INSTANCE.serializeNative(treeEntry.value.getLink(), serializedValue, 0); + else + valueSerializer.serializeNativeObject(treeEntry.value.getValue(), serializedValue, 0); + + setBinaryValue(freePointer, serializedValue); + } else { + freePointer += setLongValue(freePointer, treeEntry.leftChild); + freePointer += setLongValue(freePointer, treeEntry.rightChild); + + byte[] serializedKey = new byte[keySize]; + keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0, (Object[]) keyTypes); + setBinaryValue(freePointer, serializedKey); + + size++; + + if (updateNeighbors && size > 1) { + if (index < size - 1) { + final int nextEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE); + setLongValue(nextEntryPosition, treeEntry.rightChild); + } + + if (index > 0) { + final int prevEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index - 1) * OIntegerSerializer.INT_SIZE); + setLongValue(prevEntryPosition + OLongSerializer.LONG_SIZE, treeEntry.leftChild); + } + } + } + + return true; + } + + public int updateValue(int index, OSBTreeValue value) throws IOException { + int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); + boolean isLinkValue = getByteValue(entryPosition) > 0; + + entryPosition += OByteSerializer.BYTE_SIZE; + + int newSize = 0; + if (value.isLink()) + newSize = OLongSerializer.LONG_SIZE; + else + newSize = valueSerializer.getObjectSize(value.getValue()); + + final int oldSize; + if (isLinkValue) + oldSize = OLongSerializer.LONG_SIZE; + else + oldSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition); + + if (newSize != oldSize) + return -1; + + byte[] serializedValue = new byte[newSize]; + if (value.isLink()) + OLongSerializer.INSTANCE.serializeNative(value.getLink(), serializedValue, 0); + else + valueSerializer.serializeNativeObject(value.getValue(), serializedValue, 0); + + byte[] oldSerializedValue = getBinaryValue(entryPosition, oldSize); + + if (ODefaultComparator.INSTANCE.compare(oldSerializedValue, serializedValue) == 0) + return 0; + + setBinaryValue(entryPosition, serializedValue); + + return 1; + } + + public void setLeftSibling(long pageIndex) throws IOException { + setLongValue(LEFT_SIBLING_OFFSET, pageIndex); + } + + public long getLeftSibling() { + return getLongValue(LEFT_SIBLING_OFFSET); + } + + public void setRightSibling(long pageIndex) throws IOException { + setLongValue(RIGHT_SIBLING_OFFSET, pageIndex); + } + + public long getRightSibling() { + return getLongValue(RIGHT_SIBLING_OFFSET); + } + + public static final class SBTreeEntry implements Comparable> { + private final Comparator comparator = ODefaultComparator.INSTANCE; + + public final long leftChild; + public final long rightChild; + public final K key; + public final OSBTreeValue value; + + public SBTreeEntry(long leftChild, long rightChild, K key, OSBTreeValue value) { + this.leftChild = leftChild; + this.rightChild = rightChild; + this.key = key; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final SBTreeEntry that = (SBTreeEntry) o; + + if (leftChild != that.leftChild) + return false; + if (rightChild != that.rightChild) + return false; + if (!key.equals(that.key)) + return false; + if (value != null) { + if (!value.equals(that.value)) + return false; + } else { + if (that.value != null) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = (int) (leftChild ^ (leftChild >>> 32)); + result = 31 * result + (int) (rightChild ^ (rightChild >>> 32)); + result = 31 * result + key.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SBTreeEntry{" + "leftChild=" + leftChild + ", rightChild=" + rightChild + ", key=" + key + ", value=" + value + '}'; + } + + @Override + public int compareTo(SBTreeEntry other) { + return comparator.compare(key, other.key); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeException.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeException.java new file mode 100755 index 00000000000..10ae0d1259e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeException.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree.local; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OCoreException; +import com.orientechnologies.orient.core.exception.ODurableComponentException; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; + +/** + * @author Andrey Lomakin + * @since 8/30/13 + */ +public class OSBTreeException extends ODurableComponentException { + + public OSBTreeException(OSBTreeException exception) { + super(exception); + } + + public OSBTreeException(String message, OSBTree component) { + super(message, component); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValue.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValue.java new file mode 100644 index 00000000000..ae717abe203 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValue.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree.local; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 9/27/13 + */ +public class OSBTreeValue { + private final boolean isLink; + private final long link; + private final V value; + + public OSBTreeValue(boolean isLink, long link, V value) { + this.isLink = isLink; + this.link = link; + this.value = value; + } + + public boolean isLink() { + return isLink; + } + + public long getLink() { + return link; + } + + public V getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OSBTreeValue that = (OSBTreeValue) o; + + if (isLink != that.isLink) + return false; + if (link != that.link) + return false; + if (value != null ? !value.equals(that.value) : that.value != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = (isLink ? 1 : 0); + result = 31 * result + (int) (link ^ (link >>> 32)); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "OSBTreeValue{" + "isLink=" + isLink + ", link=" + link + ", value=" + value + '}'; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValuePage.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValuePage.java new file mode 100755 index 00000000000..036a9c55fef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtree/local/OSBTreeValuePage.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtree.local; + +import java.io.IOException; + +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +/** + * This page will contain value if it exceeds value limit for SBTree. Value is stored as list of linked pages. Following format is + * used. + *

            + *
          1. Next free list page index, or -1 if page is filled by value. 8 bytes.
          2. + *
          3. Whole value size. 4 bytes.
          4. + *
          5. Size for current page - 4 bytes.
          6. + *
          7. Next page which contains next portion of data. 8 bytes.
          8. + *
          9. Serialized value presentation.
          10. + *
          + * + * !!! This functionality should be removed after new sbtree based ridbag will be implemented, because it doest not make any sense + * to keep it, it will provide performance degradation only !!!!!! + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 9/27/13 + */ +public class OSBTreeValuePage extends ODurablePage { + private static final int FREE_LIST_NEXT_PAGE_OFFSET = NEXT_FREE_POSITION; + private static final int WHOLE_VALUE_SIZE_OFFSET = FREE_LIST_NEXT_PAGE_OFFSET + OLongSerializer.LONG_SIZE; + private static final int PAGE_VALUE_SIZE_OFFSET = WHOLE_VALUE_SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int NEXT_VALUE_PAGE_OFFSET = PAGE_VALUE_SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int BINARY_CONTENT_OFFSET = NEXT_VALUE_PAGE_OFFSET + OLongSerializer.LONG_SIZE; + + public static final int MAX_BINARY_VALUE_SIZE = MAX_PAGE_SIZE_BYTES - BINARY_CONTENT_OFFSET; + + public OSBTreeValuePage(OCacheEntry cacheEntry, OWALChanges changes, boolean isNew) throws IOException { + super(cacheEntry, changes); + + if (isNew) { + setNextFreeListPage(-1); + setNextPage(-1); + } + + } + + public void setNextPage(long nextPage) throws IOException { + setLongValue(NEXT_VALUE_PAGE_OFFSET, nextPage); + } + + public int getSize() { + return getIntValue(WHOLE_VALUE_SIZE_OFFSET); + } + + public int fillBinaryContent(byte[] data, int offset) throws IOException { + setIntValue(WHOLE_VALUE_SIZE_OFFSET, data.length); + + int maxSize = Math.min(data.length - offset, MAX_BINARY_VALUE_SIZE); + + setIntValue(PAGE_VALUE_SIZE_OFFSET, maxSize); + + byte[] pageValue = new byte[maxSize]; + System.arraycopy(data, offset, pageValue, 0, maxSize); + + setBinaryValue(BINARY_CONTENT_OFFSET, pageValue); + + return offset + maxSize; + } + + public int readBinaryContent(byte[] data, int offset) throws IOException { + int valueSize = getIntValue(PAGE_VALUE_SIZE_OFFSET); + byte[] content = getBinaryValue(BINARY_CONTENT_OFFSET, valueSize); + + System.arraycopy(content, 0, data, offset, valueSize); + + return offset + valueSize; + } + + public long getNextPage() { + return getLongValue(NEXT_VALUE_PAGE_OFFSET); + } + + public void setNextFreeListPage(long pageIndex) throws IOException { + setLongValue(FREE_LIST_NEXT_PAGE_OFFSET, pageIndex); + } + + public long getNextFreeListPage() { + return getLongValue(FREE_LIST_NEXT_PAGE_OFFSET); + } + + public static int calculateAmountOfPage(int contentSize) { + return (int) Math.ceil(1.0 * contentSize / MAX_BINARY_VALUE_SIZE); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketAbstract.java new file mode 100755 index 00000000000..2b3c339cefd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketAbstract.java @@ -0,0 +1,72 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import java.io.IOException; + +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +/** + * A base class for bonsai buckets. Bonsai bucket size is usually less than page size and one page could contain multiple bonsai + * buckets. + * + * Adds methods to read and write bucket pointers. + * + * @see com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer + * @see com.orientechnologies.orient.core.index.sbtreebonsai.local.OSBTreeBonsai + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OBonsaiBucketAbstract extends ODurablePage { + public OBonsaiBucketAbstract(OCacheEntry cacheEntry, OWALChanges changes) { + super(cacheEntry, changes); + } + + /** + * Write a bucket pointer to specific location. + * + * @param pageOffset + * where to write + * @param value + * - pointer to write + * @throws IOException + */ + protected void setBucketPointer(int pageOffset, OBonsaiBucketPointer value) throws IOException { + setLongValue(pageOffset, value.getPageIndex()); + setIntValue(pageOffset + OLongSerializer.LONG_SIZE, value.getPageOffset()); + } + + /** + * Read bucket pointer from page. + * + * @param offset + * where the pointer should be read from + * @return bucket pointer + */ + protected OBonsaiBucketPointer getBucketPointer(int offset) { + final long pageIndex = getLongValue(offset); + final int pageOffset = getIntValue(offset + OLongSerializer.LONG_SIZE); + return new OBonsaiBucketPointer(pageIndex, pageOffset); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketPointer.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketPointer.java new file mode 100644 index 00000000000..a3af4a55cbd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OBonsaiBucketPointer.java @@ -0,0 +1,87 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; + +import java.io.Serializable; + +/** + * A pointer to bucket in disk page. Defines the page and the offset in page where the bucket is placed. Is immutable. + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OBonsaiBucketPointer implements Serializable { + private static final long serialVersionUID = 1; + + public static final int SIZE = OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE; + public static final OBonsaiBucketPointer NULL = new OBonsaiBucketPointer(-1, -1); + + private final long pageIndex; + private final int pageOffset; + + public OBonsaiBucketPointer(long pageIndex, int pageOffset) { + this.pageIndex = pageIndex; + this.pageOffset = pageOffset; + } + + public long getPageIndex() { + return pageIndex; + } + + public int getPageOffset() { + return pageOffset; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OBonsaiBucketPointer that = (OBonsaiBucketPointer) o; + + if (pageIndex != that.pageIndex) + return false; + if (pageOffset != that.pageOffset) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (pageIndex ^ (pageIndex >>> 32)); + result = 31 * result + pageOffset; + return result; + } + + public boolean isValid() { + return pageIndex >= 0; + } + + @Override + public String toString() { + return "OBonsaiBucketPointer{" + "pageIndex=" + pageIndex + ", pageOffset=" + pageOffset + '}'; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsai.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsai.java new file mode 100755 index 00000000000..9f27f787556 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsai.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import java.util.Collection; +import java.util.Map; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeRidBag; +import com.orientechnologies.orient.core.storage.cache.OReadCache; +import com.orientechnologies.orient.core.index.sbtree.OTreeInternal; +import com.orientechnologies.orient.core.index.sbtree.local.OSBTree; + +/** + * The tree that have similar structure to {@link OSBTree} and designed to store small entries.
          + *
          + * The tree algorithm is the same as in {@link OSBTree}, but it have tiny buckets.
          + * The {@link OReadCache} could contain several buckets. That's why there is no huge resource consuming when you have lots of + * OSBTreeBonsai that contain only few records.
          + *
          + * + * +--------------------------------------------------------------------------------------------+
          + * | DISK CACHE PAGE |
          + * |+---------------+ +---------------+ +---------------+ +---------------+ +---------------+ |
          + * || Bonsai Bucket | | Bonsai Bucket | | Bonsai Bucket | | Bonsai Bucket | | Bonsai Bucket |...|
          + * |+---------------+ +---------------+ +---------------+ +---------------+ +---------------+ |
          + * +--------------------------------------------------------------------------------------------+
          + *
          + * + * @author Artem Orobets (enisher-at-gmail.com) + * @since 1.7rc1 + */ +public interface OSBTreeBonsai extends OTreeInternal { + /** + * Gets id of file where this bonsai tree is stored. + * + * @return id of file in {@link OReadCache} + */ + long getFileId(); + + /** + * @return the pointer to the root bucket in tree. + */ + OBonsaiBucketPointer getRootBucketPointer(); + + /** + * @return pointer to a collection. + */ + OBonsaiCollectionPointer getCollectionPointer(); + + /** + * Search for entry with specific key and return its value. + * + * @param key + * @return value associated with given key, NULL if no value is associated. + */ + V get(K key); + + boolean put(K key, V value); + + /** + * Deletes all entries from tree. + */ + void clear(); + + /** + * Deletes whole tree. After this operation tree is no longer usable. + */ + void delete(); + + long size(); + + V remove(K key); + + Collection getValuesMinor(K key, boolean inclusive, int maxValuesToFetch); + + void loadEntriesMinor(K key, boolean inclusive, RangeResultListener listener); + + Collection getValuesMajor(K key, boolean inclusive, int maxValuesToFetch); + + void loadEntriesMajor(K key, boolean inclusive, boolean ascSortOrder, RangeResultListener listener); + + Collection getValuesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, int maxValuesToFetch); + + K firstKey(); + + K lastKey(); + + void loadEntriesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, RangeResultListener listener); + + /** + * Hardcoded method for Bag to avoid creation of extra layer. + *

          + * Don't make any changes to tree. + * + * @param changes + * Bag changes + * @return real bag size + */ + int getRealBagSize(Map changes); + + OBinarySerializer getKeySerializer(); + + OBinarySerializer getValueSerializer(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiBucket.java new file mode 100755 index 00000000000..b0b7e2b9a8f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiBucket.java @@ -0,0 +1,464 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OSBTreeBonsaiLocalException; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * @author Andrey Lomakin + * @since 8/7/13 + */ +public class OSBTreeBonsaiBucket extends OBonsaiBucketAbstract { + public static final int MAX_BUCKET_SIZE_BYTES = OGlobalConfiguration.SBTREEBONSAI_BUCKET_SIZE.getValueAsInteger() * 1024; + /** + * Maximum size of key-value pair which can be put in SBTreeBonsai in bytes (24576000 by default) + */ + private static final byte LEAF = 0x1; + private static final byte DELETED = 0x2; + private static final int MAX_ENTREE_SIZE = 24576000; + private static final int FREE_POINTER_OFFSET = WAL_POSITION_OFFSET + OLongSerializer.LONG_SIZE; + private static final int SIZE_OFFSET = FREE_POINTER_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int FLAGS_OFFSET = SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + private static final int FREE_LIST_POINTER_OFFSET = FLAGS_OFFSET + OByteSerializer.BYTE_SIZE; + private static final int LEFT_SIBLING_OFFSET = FREE_LIST_POINTER_OFFSET + OBonsaiBucketPointer.SIZE; + private static final int RIGHT_SIBLING_OFFSET = LEFT_SIBLING_OFFSET + OBonsaiBucketPointer.SIZE; + private static final int TREE_SIZE_OFFSET = RIGHT_SIBLING_OFFSET + OBonsaiBucketPointer.SIZE; + private static final int KEY_SERIALIZER_OFFSET = TREE_SIZE_OFFSET + OLongSerializer.LONG_SIZE; + private static final int VALUE_SERIALIZER_OFFSET = KEY_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; + private static final int POSITIONS_ARRAY_OFFSET = VALUE_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; + private final boolean isLeaf; + private final int offset; + + private final OBinarySerializer keySerializer; + private final OBinarySerializer valueSerializer; + + private final Comparator comparator = ODefaultComparator.INSTANCE; + + private final OSBTreeBonsaiLocal tree; + + public static final class SBTreeEntry implements Map.Entry, Comparable> { + public final OBonsaiBucketPointer leftChild; + public final OBonsaiBucketPointer rightChild; + public final K key; + public final V value; + private final Comparator comparator = ODefaultComparator.INSTANCE; + + public SBTreeEntry(OBonsaiBucketPointer leftChild, OBonsaiBucketPointer rightChild, K key, V value) { + this.leftChild = leftChild; + this.rightChild = rightChild; + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException("SBTreeEntry.setValue"); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + SBTreeEntry that = (SBTreeEntry) o; + + if (!leftChild.equals(that.leftChild)) + return false; + if (!rightChild.equals(that.rightChild)) + return false; + if (!key.equals(that.key)) + return false; + if (value != null ? !value.equals(that.value) : that.value != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = leftChild.hashCode(); + result = 31 * result + rightChild.hashCode(); + result = 31 * result + key.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SBTreeEntry{" + "leftChild=" + leftChild + ", rightChild=" + rightChild + ", key=" + key + ", value=" + value + '}'; + } + + @Override + public int compareTo(SBTreeEntry other) { + return comparator.compare(key, other.key); + } + } + + public OSBTreeBonsaiBucket(OCacheEntry cacheEntry, int pageOffset, boolean isLeaf, OBinarySerializer keySerializer, + OBinarySerializer valueSerializer, OWALChanges changes, OSBTreeBonsaiLocal tree) throws IOException { + super(cacheEntry, changes); + + this.offset = pageOffset; + this.isLeaf = isLeaf; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + + setIntValue(offset + FREE_POINTER_OFFSET, MAX_BUCKET_SIZE_BYTES); + setIntValue(offset + SIZE_OFFSET, 0); + + //THIS REMOVE ALSO THE EVENTUAL DELETED FLAG + setByteValue(offset + FLAGS_OFFSET, (byte) (isLeaf ? LEAF : 0)); + setLongValue(offset + LEFT_SIBLING_OFFSET, -1); + setLongValue(offset + RIGHT_SIBLING_OFFSET, -1); + + setLongValue(offset + TREE_SIZE_OFFSET, 0); + + setByteValue(offset + KEY_SERIALIZER_OFFSET, keySerializer.getId()); + setByteValue(offset + VALUE_SERIALIZER_OFFSET, valueSerializer.getId()); + this.tree = tree; + } + + public OSBTreeBonsaiBucket(OCacheEntry cacheEntry, int pageOffset, OBinarySerializer keySerializer, + OBinarySerializer valueSerializer, OWALChanges changes, OSBTreeBonsaiLocal tree) { + super(cacheEntry, changes); + + this.offset = pageOffset; + this.isLeaf = (getByteValue(offset + FLAGS_OFFSET) & LEAF) == LEAF; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + this.tree = tree; + } + + public byte getKeySerializerId() { + return getByteValue(offset + KEY_SERIALIZER_OFFSET); + } + + public void setKeySerializerId(byte keySerializerId) { + setByteValue(offset + KEY_SERIALIZER_OFFSET, keySerializerId); + } + + public byte getValueSerializerId() { + return getByteValue(offset + VALUE_SERIALIZER_OFFSET); + } + + public void setValueSerializerId(byte valueSerializerId) { + setByteValue(offset + VALUE_SERIALIZER_OFFSET, valueSerializerId); + } + + public long getTreeSize() { + return getLongValue(offset + TREE_SIZE_OFFSET); + } + + public void setTreeSize(long size) throws IOException { + setLongValue(offset + TREE_SIZE_OFFSET, size); + } + + public boolean isEmpty() { + return size() == 0; + } + + public int find(K key) { + int low = 0; + int high = size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + K midVal = getKey(mid); + int cmp = comparator.compare(midVal, key); + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + public void remove(int entryIndex) throws IOException { + int entryPosition = getIntValue(offset + POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE); + + int entrySize = getObjectSizeInDirectMemory(keySerializer, offset + entryPosition); + if (isLeaf) { + assert valueSerializer.isFixedLength(); + entrySize += valueSerializer.getFixedLength(); + } else { + throw new IllegalStateException("Remove is applies to leaf buckets only"); + } + + int size = size(); + if (entryIndex < size - 1) { + moveData(offset + POSITIONS_ARRAY_OFFSET + (entryIndex + 1) * OIntegerSerializer.INT_SIZE, offset + POSITIONS_ARRAY_OFFSET + + entryIndex * OIntegerSerializer.INT_SIZE, (size - entryIndex - 1) * OIntegerSerializer.INT_SIZE); + } + + size--; + setIntValue(offset + SIZE_OFFSET, size); + + int freePointer = getIntValue(offset + FREE_POINTER_OFFSET); + if (size > 0 && entryPosition > freePointer) { + moveData(offset + freePointer, offset + freePointer + entrySize, entryPosition - freePointer); + } + setIntValue(offset + FREE_POINTER_OFFSET, freePointer + entrySize); + + int currentPositionOffset = offset + POSITIONS_ARRAY_OFFSET; + + for (int i = 0; i < size; i++) { + int currentEntryPosition = getIntValue(currentPositionOffset); + if (currentEntryPosition < entryPosition) + setIntValue(currentPositionOffset, currentEntryPosition + entrySize); + currentPositionOffset += OIntegerSerializer.INT_SIZE; + } + } + + public int size() { + return getIntValue(offset + SIZE_OFFSET); + } + + public SBTreeEntry getEntry(int entryIndex) { + int entryPosition = getIntValue(offset + entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + + if (isLeaf) { + K key = deserializeFromDirectMemory(keySerializer, offset + entryPosition); + entryPosition += getObjectSizeInDirectMemory(keySerializer, offset + entryPosition); + + V value = deserializeFromDirectMemory(valueSerializer, offset + entryPosition); + + return new SBTreeEntry(OBonsaiBucketPointer.NULL, OBonsaiBucketPointer.NULL, key, value); + } else { + OBonsaiBucketPointer leftChild = getBucketPointer(offset + entryPosition); + entryPosition += OBonsaiBucketPointer.SIZE; + + OBonsaiBucketPointer rightChild = getBucketPointer(offset + entryPosition); + entryPosition += OBonsaiBucketPointer.SIZE; + + K key = deserializeFromDirectMemory(keySerializer, offset + entryPosition); + + return new SBTreeEntry(leftChild, rightChild, key, null); + } + } + + public K getKey(int index) { + int entryPosition = getIntValue(offset + index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + + if (!isLeaf) + entryPosition += 2 * (OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE); + + return deserializeFromDirectMemory(keySerializer, offset + entryPosition); + } + + public boolean isLeaf() { + return isLeaf; + } + + public void addAll(List> entries) throws IOException { + for (int i = 0; i < entries.size(); i++) + addEntry(i, entries.get(i), false); + } + + public void shrink(int newSize) throws IOException { + List> treeEntries = new ArrayList>(newSize); + + for (int i = 0; i < newSize; i++) { + treeEntries.add(getEntry(i)); + } + + setIntValue(offset + FREE_POINTER_OFFSET, MAX_BUCKET_SIZE_BYTES); + setIntValue(offset + SIZE_OFFSET, 0); + + int index = 0; + for (SBTreeEntry entry : treeEntries) { + addEntry(index, entry, false); + index++; + } + } + + public boolean addEntry(int index, SBTreeEntry treeEntry, boolean updateNeighbors) throws IOException { + final int keySize = keySerializer.getObjectSize(treeEntry.key); + int valueSize = 0; + int entrySize = keySize; + + if (isLeaf) { + assert valueSerializer.isFixedLength(); + valueSize = valueSerializer.getFixedLength(); + + entrySize += valueSize; + + checkEntreeSize(entrySize); + } else + entrySize += 2 * (OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE); + + int size = size(); + int freePointer = getIntValue(offset + FREE_POINTER_OFFSET); + if (freePointer - entrySize < (size + 1) * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) { + if (size > 1) + return false; + else + throw new OSBTreeBonsaiLocalException("Entry size ('key + value') is more than is more than allowed " + + (freePointer - 2 * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) + + " bytes, either increase page size using '" + OGlobalConfiguration.SBTREEBONSAI_BUCKET_SIZE.getKey() + + "' parameter, or decrease 'key + value' size.", tree); + } + + if (index <= size - 1) { + moveData(offset + POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, offset + POSITIONS_ARRAY_OFFSET + (index + 1) + * OIntegerSerializer.INT_SIZE, (size - index) * OIntegerSerializer.INT_SIZE); + } + + freePointer -= entrySize; + + setIntValue(offset + FREE_POINTER_OFFSET, freePointer); + setIntValue(offset + POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, freePointer); + setIntValue(offset + SIZE_OFFSET, size + 1); + + if (isLeaf) { + byte[] serializedKey = new byte[keySize]; + keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0); + + setBinaryValue(offset + freePointer, serializedKey); + freePointer += keySize; + + byte[] serializedValue = new byte[valueSize]; + valueSerializer.serializeNativeObject(treeEntry.value, serializedValue, 0); + setBinaryValue(offset + freePointer, serializedValue); + + } else { + setBucketPointer(offset + freePointer, treeEntry.leftChild); + freePointer += OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE; + + setBucketPointer(offset + freePointer, treeEntry.rightChild); + freePointer += OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE; + + byte[] serializedKey = new byte[keySize]; + keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0); + setBinaryValue(offset + freePointer, serializedKey); + + size++; + + if (updateNeighbors && size > 1) { + if (index < size - 1) { + final int nextEntryPosition = getIntValue(offset + POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE); + setBucketPointer(offset + nextEntryPosition, treeEntry.rightChild); + } + + if (index > 0) { + final int prevEntryPosition = getIntValue(offset + POSITIONS_ARRAY_OFFSET + (index - 1) * OIntegerSerializer.INT_SIZE); + setBucketPointer(offset + prevEntryPosition + OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE, + treeEntry.leftChild); + } + } + } + + return true; + } + + public int updateValue(int index, V value) throws IOException { + assert valueSerializer.isFixedLength(); + + int entryPosition = getIntValue(offset + index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); + entryPosition += getObjectSizeInDirectMemory(keySerializer, offset + entryPosition); + + final int size = valueSerializer.getFixedLength(); + + byte[] serializedValue = new byte[size]; + valueSerializer.serializeNativeObject(value, serializedValue, 0); + + byte[] oldSerializedValue = getBinaryValue(offset + entryPosition, size); + + if (ODefaultComparator.INSTANCE.compare(oldSerializedValue, serializedValue) == 0) + return 0; + + setBinaryValue(offset + entryPosition, serializedValue); + + return 1; + } + + public OBonsaiBucketPointer getFreeListPointer() { + return getBucketPointer(offset + FREE_LIST_POINTER_OFFSET); + } + + public void setFreeListPointer(OBonsaiBucketPointer pointer) throws IOException { + setBucketPointer(offset + FREE_LIST_POINTER_OFFSET, pointer); + } + + public void setDelted(boolean deleted) { + byte value = getByteValue(offset + FLAGS_OFFSET); + if(deleted) + setByteValue(offset + FLAGS_OFFSET, (byte) (value | DELETED)); + else + //REMOVE THE FLAG the &(and) ~(not) is the opreation to remove flags in bits + setByteValue(offset + FLAGS_OFFSET, (byte) (value & (~DELETED))); + } + + public boolean isDeleted() { + return (getByteValue(offset + FLAGS_OFFSET) & DELETED) == DELETED; + } + + + public OBonsaiBucketPointer getLeftSibling() { + return getBucketPointer(offset + LEFT_SIBLING_OFFSET); + } + + public void setLeftSibling(OBonsaiBucketPointer pointer) throws IOException { + setBucketPointer(offset + LEFT_SIBLING_OFFSET, pointer); + } + + public OBonsaiBucketPointer getRightSibling() { + return getBucketPointer(offset + RIGHT_SIBLING_OFFSET); + } + + public void setRightSibling(OBonsaiBucketPointer pointer) throws IOException { + setBucketPointer(offset + RIGHT_SIBLING_OFFSET, pointer); + } + + private void checkEntreeSize(int entreeSize) { + if (entreeSize > MAX_ENTREE_SIZE) + throw new OSBTreeBonsaiLocalException("Serialized key-value pair size bigger than allowed " + entreeSize + " vs " + + MAX_ENTREE_SIZE + ".", tree); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiLocal.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiLocal.java new file mode 100755 index 00000000000..60c7a7974db --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSBTreeBonsaiLocal.java @@ -0,0 +1,1625 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import com.orientechnologies.common.comparator.ODefaultComparator; +import com.orientechnologies.common.concur.lock.OLockManager; +import com.orientechnologies.common.concur.lock.OPartitionedLockManager; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.types.OModifiableInteger; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OBonsaiCollectionPointer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OSBTreeRidBag; +import com.orientechnologies.orient.core.exception.OSBTreeBonsaiLocalException; +import com.orientechnologies.orient.core.index.sbtree.local.OSBTree; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation; +import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent; +import com.orientechnologies.orient.core.storage.impl.local.statistic.OSessionStoragePerformanceStatistic; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.concurrent.locks.Lock; + +/** + * Tree-based dictionary algorithm. Similar to {@link OSBTree} but uses subpages of disk cache that is more efficient for small data + * structures. + *

          + * Oriented for usage of several instances inside of one file. + *

          + * Creation of several instances that represent the same collection is not allowed. + * + * @author Andrey Lomakin + * @author Artem Orobets + * @see OSBTree + * @since 1.6.0 + */ +public class OSBTreeBonsaiLocal extends ODurableComponent implements OSBTreeBonsai { + private static final OLockManager FILE_LOCK_MANAGER = new OPartitionedLockManager(); + + private static final int PAGE_SIZE = + OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024; + private final float freeSpaceReuseTrigger = OGlobalConfiguration.SBTREEBOSAI_FREE_SPACE_REUSE_TRIGGER + .getValueAsFloat(); + private static final OBonsaiBucketPointer SYS_BUCKET = new OBonsaiBucketPointer(0, 0); + + private OBonsaiBucketPointer rootBucketPointer; + + private final Comparator comparator = ODefaultComparator.INSTANCE; + + private volatile Long fileId = -1l; + + private OBinarySerializer keySerializer; + private OBinarySerializer valueSerializer; + + public OSBTreeBonsaiLocal(String name, String dataFileExtension, OAbstractPaginatedStorage storage) { + super(storage, name, dataFileExtension, name + dataFileExtension); + } + + public void create(OBinarySerializer keySerializer, OBinarySerializer valueSerializer) { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during sbtree creation", this), e); + } + + Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(-1l); + try { + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + + if (isFileExists(atomicOperation, getFullName())) + this.fileId = openFile(atomicOperation, getFullName()); + else + this.fileId = addFile(atomicOperation, getFullName()); + + initAfterCreate(atomicOperation); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error creation of sbtree with name" + getName(), this), e); + } catch (Exception e) { + rollback(e); + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error creation of sbtree with name" + getName(), this), e); + } finally { + lock.unlock(); + } + } finally { + completeOperation(); + } + } + + private void initAfterCreate(OAtomicOperation atomicOperation) throws IOException { + initSysBucket(atomicOperation); + + final AllocationResult allocationResult = allocateBucket(atomicOperation); + OCacheEntry rootCacheEntry = allocationResult.getCacheEntry(); + this.rootBucketPointer = allocationResult.getPointer(); + + rootCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket rootBucket = new OSBTreeBonsaiBucket(rootCacheEntry, this.rootBucketPointer.getPageOffset(), + true, keySerializer, valueSerializer, getChanges(atomicOperation, rootCacheEntry), this); + rootBucket.setTreeSize(0); + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + } + + @Override + public long getFileId() { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + return fileId; + } finally { + lock.unlock(); + } + } + + @Override + public OBonsaiBucketPointer getRootBucketPointer() { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + return rootBucketPointer; + } finally { + lock.unlock(); + } + } + + @Override + public OBonsaiCollectionPointer getCollectionPointer() { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + return new OBonsaiCollectionPointer(fileId, rootBucketPointer); + } finally { + lock.unlock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } + + @Override + public V get(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + if (bucketSearchResult.itemIndex < 0) + return null; + + OBonsaiBucketPointer bucketPointer = bucketSearchResult.getLastPathItem(); + + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + keyBucketCacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket keyBucket = new OSBTreeBonsaiBucket(keyBucketCacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, keyBucketCacheEntry), this); + return keyBucket.getEntry(bucketSearchResult.itemIndex).value; + } finally { + keyBucketCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + } + } finally { + lock.unlock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during retrieving of sbtree with name " + getName(), this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(1); + completeOperation(); + } + } + + @Override + public boolean put(K key, V value) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryUpdateTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during sbtree entrie put", this), e); + } + + final Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + OBonsaiBucketPointer bucketPointer = bucketSearchResult.getLastPathItem(); + + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + keyBucketCacheEntry.acquireExclusiveLock(); + OSBTreeBonsaiBucket keyBucket = new OSBTreeBonsaiBucket(keyBucketCacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, keyBucketCacheEntry), this); + + final boolean itemFound = bucketSearchResult.itemIndex >= 0; + boolean result = true; + if (itemFound) { + final int updateResult = keyBucket.updateValue(bucketSearchResult.itemIndex, value); + assert updateResult == 0 || updateResult == 1; + + result = updateResult != 0; + } else { + int insertionIndex = -bucketSearchResult.itemIndex - 1; + + while (!keyBucket.addEntry(insertionIndex, + new OSBTreeBonsaiBucket.SBTreeEntry(OBonsaiBucketPointer.NULL, OBonsaiBucketPointer.NULL, key, value), true)) { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + + bucketSearchResult = splitBucket(bucketSearchResult.path, insertionIndex, key, atomicOperation); + bucketPointer = bucketSearchResult.getLastPathItem(); + + insertionIndex = bucketSearchResult.itemIndex; + + keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketSearchResult.getLastPathItem().getPageIndex(), false); + keyBucketCacheEntry.acquireExclusiveLock(); + + keyBucket = new OSBTreeBonsaiBucket(keyBucketCacheEntry, bucketPointer.getPageOffset(), keySerializer, + valueSerializer, getChanges(atomicOperation, keyBucketCacheEntry), this); + } + } + + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + + if (!itemFound) + setSize(size() + 1, atomicOperation); + + endAtomicOperation(false, null); + return result; + } catch (IOException e) { + rollback(e); + throw OException.wrapException( + new OSBTreeBonsaiLocalException("Error during index update with key " + key + " and value " + value, this), e); + } finally { + lock.unlock(); + } + + } finally { + if (statistic != null) + statistic.stopRidBagEntryUpdateTimer(); + completeOperation(); + } + } + + private void rollback(Exception e) { + try { + endAtomicOperation(true, e); + } catch (IOException e1) { + OLogManager.instance().error(this, "Error during sbtree operation rollback", e1); + } + } + + public void close(boolean flush) { + startOperation(); + try { + Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + readCache.closeFile(fileId, flush, writeCache); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during close of index " + getName(), this), e); + } finally { + lock.unlock(); + } + } finally { + completeOperation(); + } + } + + public void close() { + close(true); + } + + /** + * Removes all entries from bonsai tree. Put all but the root page to free list for further reuse. + */ + @Override + public void clear() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during sbtree entrie clear", this), e); + } + + final Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + final Queue subTreesToDelete = new LinkedList(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, rootBucketPointer.getPageIndex(), false); + cacheEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket rootBucket = new OSBTreeBonsaiBucket(cacheEntry, rootBucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + addChildrenToQueue(subTreesToDelete, rootBucket); + + rootBucket.shrink(0); + rootBucket = new OSBTreeBonsaiBucket(cacheEntry, rootBucketPointer.getPageOffset(), true, keySerializer, + valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + rootBucket.setTreeSize(0); + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + + recycleSubTrees(subTreesToDelete, atomicOperation); + + endAtomicOperation(false, null); + } catch (IOException e) { + rollback(e); + + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during clear of sbtree with name " + getName(), this), e); + } catch (RuntimeException e) { + rollback(e); + + throw e; + } finally { + lock.unlock(); + } + } finally { + completeOperation(); + } + } + + private void addChildrenToQueue(Queue subTreesToDelete, OSBTreeBonsaiBucket rootBucket) { + if (!rootBucket.isLeaf()) { + final int size = rootBucket.size(); + if (size > 0) + subTreesToDelete.add(rootBucket.getEntry(0).leftChild); + + for (int i = 0; i < size; i++) { + final OSBTreeBonsaiBucket.SBTreeEntry entry = rootBucket.getEntry(i); + subTreesToDelete.add(entry.rightChild); + } + } + } + + private void recycleSubTrees(Queue subTreesToDelete, OAtomicOperation atomicOperation) throws IOException { + OBonsaiBucketPointer head = OBonsaiBucketPointer.NULL; + OBonsaiBucketPointer tail = subTreesToDelete.peek(); + + int bucketCount = 0; + while (!subTreesToDelete.isEmpty()) { + final OBonsaiBucketPointer bucketPointer = subTreesToDelete.poll(); + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireExclusiveLock(); + try { + final OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + addChildrenToQueue(subTreesToDelete, bucket); + + bucket.setFreeListPointer(head); + bucket.setDelted(true); + head = bucketPointer; + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + bucketCount++; + } + + if (head.isValid()) { + final OCacheEntry sysCacheEntry = loadPage(atomicOperation, fileId, SYS_BUCKET.getPageIndex(), false); + sysCacheEntry.acquireExclusiveLock(); + try { + final OSysBucket sysBucket = new OSysBucket(sysCacheEntry, getChanges(atomicOperation, sysCacheEntry)); + + attachFreeListHead(tail, sysBucket.getFreeListHead(), atomicOperation); + sysBucket.setFreeListHead(head); + sysBucket.setFreeListLength(sysBucket.freeListLength() + bucketCount); + + } finally { + sysCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, sysCacheEntry); + } + } + } + + private void attachFreeListHead(OBonsaiBucketPointer bucketPointer, OBonsaiBucketPointer freeListHead, + OAtomicOperation atomicOperation) throws IOException { + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireExclusiveLock(); + try { + final OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + bucket.setFreeListPointer(freeListHead); + } finally { + cacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, cacheEntry); + } + } + + /** + * Deletes a whole tree. Puts all its pages to free list for further reusage. + */ + @Override + public void delete() { + startOperation(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(false); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during sbtree deletion", this), e); + } + + final Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + final Queue subTreesToDelete = new LinkedList(); + subTreesToDelete.add(rootBucketPointer); + recycleSubTrees(subTreesToDelete, atomicOperation); + + endAtomicOperation(false, null); + } catch (Exception e) { + rollback(e); + + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during delete of sbtree with name " + getName(), this), e); + } finally { + lock.unlock(); + } + } finally { + completeOperation(); + } + } + + public boolean load(OBonsaiBucketPointer rootBucketPointer) { + OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryLoadTimer(); + try { + Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + this.rootBucketPointer = rootBucketPointer; + + final OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + this.fileId = openFile(atomicOperation, getFullName()); + + OCacheEntry rootCacheEntry = loadPage(atomicOperation, this.fileId, this.rootBucketPointer.getPageIndex(), false); + + rootCacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket rootBucket = new OSBTreeBonsaiBucket(rootCacheEntry, + this.rootBucketPointer.getPageOffset(), keySerializer, valueSerializer, getChanges(atomicOperation, rootCacheEntry), + this); + keySerializer = (OBinarySerializer) storage.getComponentsFactory().binarySerializerFactory + .getObjectSerializer(rootBucket.getKeySerializerId()); + valueSerializer = (OBinarySerializer) storage.getComponentsFactory().binarySerializerFactory + .getObjectSerializer(rootBucket.getValueSerializerId()); + + return !rootBucket.isDeleted(); + + } finally { + rootCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, rootCacheEntry); + } + + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Exception during loading of sbtree " + fileId, this), e); + } finally { + lock.unlock(); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryLoadTimer(); + completeOperation(); + } + } + + private void setSize(long size, OAtomicOperation atomicOperation) throws IOException { + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, rootBucketPointer.getPageIndex(), false); + + rootCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket rootBucket = new OSBTreeBonsaiBucket(rootCacheEntry, rootBucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, rootCacheEntry), this); + rootBucket.setTreeSize(size); + } finally { + rootCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rootCacheEntry); + } + } + + @Override + public long size() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + OCacheEntry rootCacheEntry = loadPage(atomicOperation, fileId, rootBucketPointer.getPageIndex(), false); + rootCacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket rootBucket = new OSBTreeBonsaiBucket(rootCacheEntry, rootBucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, rootCacheEntry), this); + return rootBucket.getTreeSize(); + } finally { + rootCacheEntry.releaseSharedLock(); + releasePage(atomicOperation, rootCacheEntry); + } + } finally { + lock.unlock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during retrieving of size of index " + getName(), this), e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + @Override + public V remove(K key) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryDeletionTimer(); + try { + final OAtomicOperation atomicOperation; + try { + atomicOperation = startAtomicOperation(true); + } catch (IOException e) { + throw OException.wrapException(new OSBTreeBonsaiLocalException("Error during sbtree entrie removal", this), e); + } + + Lock lock = FILE_LOCK_MANAGER.acquireExclusiveLock(fileId); + try { + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + if (bucketSearchResult.itemIndex < 0) { + endAtomicOperation(false, null); + return null; + } + + OBonsaiBucketPointer bucketPointer = bucketSearchResult.getLastPathItem(); + + OCacheEntry keyBucketCacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + final V removed; + + keyBucketCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket keyBucket = new OSBTreeBonsaiBucket(keyBucketCacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, keyBucketCacheEntry), this); + + removed = keyBucket.getEntry(bucketSearchResult.itemIndex).value; + + keyBucket.remove(bucketSearchResult.itemIndex); + } finally { + keyBucketCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, keyBucketCacheEntry); + } + setSize(size() - 1, atomicOperation); + + endAtomicOperation(false, null); + return removed; + } catch (IOException e) { + rollback(e); + + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during removing key " + key + " from sbtree " + getName(), this), + e); + } catch (RuntimeException e) { + rollback(e); + + throw e; + } finally { + lock.unlock(); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryDeletionTimer(); + completeOperation(); + } + } + + @Override + public Collection getValuesMinor(K key, boolean inclusive, final int maxValuesToFetch) { + startOperation(); + try { + final List result = new ArrayList(); + + loadEntriesMinor(key, inclusive, new RangeResultListener() { + @Override + public boolean addResult(Map.Entry entry) { + result.add(entry.getValue()); + if (maxValuesToFetch > -1 && result.size() >= maxValuesToFetch) + return false; + + return true; + } + }); + + return result; + } finally { + completeOperation(); + } + } + + @Override + public void loadEntriesMinor(K key, boolean inclusive, RangeResultListener listener) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + int readEntries = 0; + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + + OBonsaiBucketPointer bucketPointer = bucketSearchResult.getLastPathItem(); + int index; + if (bucketSearchResult.itemIndex >= 0) { + index = inclusive ? bucketSearchResult.itemIndex : bucketSearchResult.itemIndex - 1; + } else { + index = -bucketSearchResult.itemIndex - 2; + } + + boolean firstBucket = true; + do { + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + if (!firstBucket) + index = bucket.size() - 1; + + for (int i = index; i >= 0; i--) { + if (!listener.addResult(bucket.getEntry(i))) { + readEntries++; + return; + } + + } + + bucketPointer = bucket.getLeftSibling(); + + firstBucket = false; + + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } while (bucketPointer.getPageIndex() >= 0); + } finally { + lock.unlock(); + } + } catch (IOException ioe) { + throw OException.wrapException( + new OSBTreeBonsaiLocalException("Error during fetch of minor values for key " + key + " in sbtree " + getName(), this), + ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(readEntries); + completeOperation(); + } + } + + @Override + public Collection getValuesMajor(K key, boolean inclusive, final int maxValuesToFetch) { + startOperation(); + try { + final List result = new ArrayList(); + + loadEntriesMajor(key, inclusive, true, new RangeResultListener() { + @Override + public boolean addResult(Map.Entry entry) { + result.add(entry.getValue()); + if (maxValuesToFetch > -1 && result.size() >= maxValuesToFetch) + return false; + + return true; + } + }); + + return result; + } finally { + completeOperation(); + } + } + + /** + * Load all entries with key greater then specified key. + * + * @param key defines + * @param inclusive if true entry with given key is included + * @param ascSortOrder + * @param listener + */ + @Override + public void loadEntriesMajor(K key, boolean inclusive, boolean ascSortOrder, RangeResultListener listener) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + int readEntries = 0; + + startOperation(); + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + if (!ascSortOrder) + throw new IllegalStateException("Descending sort order is not supported."); + + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + BucketSearchResult bucketSearchResult = findBucket(key, atomicOperation); + OBonsaiBucketPointer bucketPointer = bucketSearchResult.getLastPathItem(); + + int index; + if (bucketSearchResult.itemIndex >= 0) { + index = inclusive ? bucketSearchResult.itemIndex : bucketSearchResult.itemIndex + 1; + } else { + index = -bucketSearchResult.itemIndex - 1; + } + + do { + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + int bucketSize = bucket.size(); + for (int i = index; i < bucketSize; i++) { + if (!listener.addResult(bucket.getEntry(i))) { + readEntries++; + return; + } + + } + + bucketPointer = bucket.getRightSibling(); + index = 0; + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + + } while (bucketPointer.getPageIndex() >= 0); + } finally { + lock.unlock(); + } + } catch (IOException ioe) { + throw OException.wrapException( + new OSBTreeBonsaiLocalException("Error during fetch of major values for key " + key + " in sbtree " + getName(), this), + ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(readEntries); + completeOperation(); + } + } + + @Override + public Collection getValuesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, + final int maxValuesToFetch) { + startOperation(); + try { + final List result = new ArrayList(); + loadEntriesBetween(keyFrom, fromInclusive, keyTo, toInclusive, new RangeResultListener() { + @Override + public boolean addResult(Map.Entry entry) { + result.add(entry.getValue()); + if (maxValuesToFetch > 0 && result.size() >= maxValuesToFetch) + return false; + + return true; + } + }); + + return result; + } finally { + completeOperation(); + } + } + + @Override + public K firstKey() { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + LinkedList path = new LinkedList(); + + OBonsaiBucketPointer bucketPointer = rootBucketPointer; + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, rootBucketPointer.getPageIndex(), false); + int itemIndex = 0; + cacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + while (true) { + if (bucket.isLeaf()) { + if (bucket.isEmpty()) { + if (path.isEmpty()) { + return null; + } else { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketPointer = pagePathItemUnit.bucketPointer; + itemIndex = pagePathItemUnit.itemIndex + 1; + } + } else { + return bucket.getKey(0); + } + } else { + if (bucket.isEmpty() || itemIndex > bucket.size()) { + if (path.isEmpty()) { + return null; + } else { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketPointer = pagePathItemUnit.bucketPointer; + itemIndex = pagePathItemUnit.itemIndex + 1; + } + } else { + path.add(new PagePathItemUnit(bucketPointer, itemIndex)); + + if (itemIndex < bucket.size()) { + OSBTreeBonsaiBucket.SBTreeEntry entry = bucket.getEntry(itemIndex); + bucketPointer = entry.leftChild; + } else { + OSBTreeBonsaiBucket.SBTreeEntry entry = bucket.getEntry(itemIndex - 1); + bucketPointer = entry.rightChild; + } + + itemIndex = 0; + } + } + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + + bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), keySerializer, valueSerializer, + getChanges(atomicOperation, cacheEntry), this); + } + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + lock.unlock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during finding first key in sbtree [" + getName() + "]", this), + e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(1); + completeOperation(); + } + } + + @Override + public K lastKey() { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + startOperation(); + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + LinkedList path = new LinkedList(); + + OBonsaiBucketPointer bucketPointer = rootBucketPointer; + + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), keySerializer, + valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + int itemIndex = bucket.size() - 1; + try { + while (true) { + if (bucket.isLeaf()) { + if (bucket.isEmpty()) { + if (path.isEmpty()) { + return null; + } else { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketPointer = pagePathItemUnit.bucketPointer; + itemIndex = pagePathItemUnit.itemIndex - 1; + } + } else { + return bucket.getKey(bucket.size() - 1); + } + } else { + if (itemIndex < -1) { + if (!path.isEmpty()) { + PagePathItemUnit pagePathItemUnit = path.removeLast(); + + bucketPointer = pagePathItemUnit.bucketPointer; + itemIndex = pagePathItemUnit.itemIndex - 1; + } else + return null; + } else { + path.add(new PagePathItemUnit(bucketPointer, itemIndex)); + + if (itemIndex > -1) { + OSBTreeBonsaiBucket.SBTreeEntry entry = bucket.getEntry(itemIndex); + bucketPointer = entry.rightChild; + } else { + OSBTreeBonsaiBucket.SBTreeEntry entry = bucket.getEntry(0); + bucketPointer = entry.leftChild; + } + + itemIndex = OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES + 1; + } + } + + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + + cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + + bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), keySerializer, valueSerializer, + getChanges(atomicOperation, cacheEntry), this); + if (itemIndex == OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES + 1) + itemIndex = bucket.size() - 1; + } + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + } finally { + lock.unlock(); + } + } catch (IOException e) { + throw OException + .wrapException(new OSBTreeBonsaiLocalException("Error during finding first key in sbtree [" + getName() + "]", this), + e); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(1); + completeOperation(); + } + } + + @Override + public void loadEntriesBetween(K keyFrom, boolean fromInclusive, K keyTo, boolean toInclusive, + RangeResultListener listener) { + final OSessionStoragePerformanceStatistic statistic = performanceStatisticManager.getSessionPerformanceStatistic(); + int readEntries = 0; + + startOperation(); + if (statistic != null) + statistic.startRidBagEntryReadTimer(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + OAtomicOperation atomicOperation = atomicOperationsManager.getCurrentOperation(); + BucketSearchResult bucketSearchResultFrom = findBucket(keyFrom, atomicOperation); + + OBonsaiBucketPointer bucketPointerFrom = bucketSearchResultFrom.getLastPathItem(); + + int indexFrom; + if (bucketSearchResultFrom.itemIndex >= 0) { + indexFrom = fromInclusive ? bucketSearchResultFrom.itemIndex : bucketSearchResultFrom.itemIndex + 1; + } else { + indexFrom = -bucketSearchResultFrom.itemIndex - 1; + } + + BucketSearchResult bucketSearchResultTo = findBucket(keyTo, atomicOperation); + OBonsaiBucketPointer bucketPointerTo = bucketSearchResultTo.getLastPathItem(); + + int indexTo; + if (bucketSearchResultTo.itemIndex >= 0) { + indexTo = toInclusive ? bucketSearchResultTo.itemIndex : bucketSearchResultTo.itemIndex - 1; + } else { + indexTo = -bucketSearchResultTo.itemIndex - 2; + } + + int startIndex = indexFrom; + int endIndex; + OBonsaiBucketPointer bucketPointer = bucketPointerFrom; + + resultsLoop: + while (true) { + + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + cacheEntry.acquireSharedLock(); + try { + OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + if (!bucketPointer.equals(bucketPointerTo)) + endIndex = bucket.size() - 1; + else + endIndex = indexTo; + + for (int i = startIndex; i <= endIndex; i++) { + if (!listener.addResult(bucket.getEntry(i))) { + readEntries++; + break resultsLoop; + } + + } + + if (bucketPointer.equals(bucketPointerTo)) + break; + + bucketPointer = bucket.getRightSibling(); + if (bucketPointer.getPageIndex() < 0) + break; + + } finally { + cacheEntry.releaseSharedLock(); + releasePage(atomicOperation, cacheEntry); + } + + startIndex = 0; + } + } finally { + lock.unlock(); + } + } catch (IOException ioe) { + throw OException.wrapException(new OSBTreeBonsaiLocalException( + "Error during fetch of values between key " + keyFrom + " and key " + keyTo + " in sbtree " + getName(), this), ioe); + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + if (statistic != null) + statistic.stopRidBagEntryReadTimer(readEntries); + completeOperation(); + } + } + + public void flush() { + startOperation(); + try { + atomicOperationsManager.acquireReadLock(this); + try { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + writeCache.flush(); + } finally { + lock.unlock(); + } + } finally { + atomicOperationsManager.releaseReadLock(this); + } + } finally { + completeOperation(); + } + } + + private BucketSearchResult splitBucket(List path, int keyIndex, K keyToInsert, + OAtomicOperation atomicOperation) throws IOException { + final OBonsaiBucketPointer bucketPointer = path.get(path.size() - 1); + + OCacheEntry bucketEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + + bucketEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket bucketToSplit = new OSBTreeBonsaiBucket(bucketEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, bucketEntry), this); + + final boolean splitLeaf = bucketToSplit.isLeaf(); + final int bucketSize = bucketToSplit.size(); + + int indexToSplit = bucketSize >>> 1; + final K separationKey = bucketToSplit.getKey(indexToSplit); + final List> rightEntries = new ArrayList>( + indexToSplit); + + final int startRightIndex = splitLeaf ? indexToSplit : indexToSplit + 1; + + for (int i = startRightIndex; i < bucketSize; i++) + rightEntries.add(bucketToSplit.getEntry(i)); + + if (!bucketPointer.equals(rootBucketPointer)) { + final AllocationResult allocationResult = allocateBucket(atomicOperation); + OCacheEntry rightBucketEntry = allocationResult.getCacheEntry(); + final OBonsaiBucketPointer rightBucketPointer = allocationResult.getPointer(); + rightBucketEntry.acquireExclusiveLock(); + + try { + OSBTreeBonsaiBucket newRightBucket = new OSBTreeBonsaiBucket(rightBucketEntry, + rightBucketPointer.getPageOffset(), splitLeaf, keySerializer, valueSerializer, + getChanges(atomicOperation, rightBucketEntry), this); + newRightBucket.addAll(rightEntries); + + bucketToSplit.shrink(indexToSplit); + + if (splitLeaf) { + OBonsaiBucketPointer rightSiblingBucketPointer = bucketToSplit.getRightSibling(); + + newRightBucket.setRightSibling(rightSiblingBucketPointer); + newRightBucket.setLeftSibling(bucketPointer); + + bucketToSplit.setRightSibling(rightBucketPointer); + + if (rightSiblingBucketPointer.isValid()) { + final OCacheEntry rightSiblingBucketEntry = loadPage(atomicOperation, fileId, + rightSiblingBucketPointer.getPageIndex(), false); + + rightSiblingBucketEntry.acquireExclusiveLock(); + OSBTreeBonsaiBucket rightSiblingBucket = new OSBTreeBonsaiBucket(rightSiblingBucketEntry, + rightSiblingBucketPointer.getPageOffset(), keySerializer, valueSerializer, + getChanges(atomicOperation, rightSiblingBucketEntry), this); + try { + rightSiblingBucket.setLeftSibling(rightBucketPointer); + } finally { + rightSiblingBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightSiblingBucketEntry); + } + } + } + + OBonsaiBucketPointer parentBucketPointer = path.get(path.size() - 2); + OCacheEntry parentCacheEntry = loadPage(atomicOperation, fileId, parentBucketPointer.getPageIndex(), false); + + parentCacheEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket parentBucket = new OSBTreeBonsaiBucket(parentCacheEntry, + parentBucketPointer.getPageOffset(), keySerializer, valueSerializer, getChanges(atomicOperation, parentCacheEntry), + this); + OSBTreeBonsaiBucket.SBTreeEntry parentEntry = new OSBTreeBonsaiBucket.SBTreeEntry(bucketPointer, + rightBucketPointer, separationKey, null); + + int insertionIndex = parentBucket.find(separationKey); + assert insertionIndex < 0; + + insertionIndex = -insertionIndex - 1; + while (!parentBucket.addEntry(insertionIndex, parentEntry, true)) { + parentCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, parentCacheEntry); + + BucketSearchResult bucketSearchResult = splitBucket(path.subList(0, path.size() - 1), insertionIndex, separationKey, + atomicOperation); + + parentBucketPointer = bucketSearchResult.getLastPathItem(); + parentCacheEntry = loadPage(atomicOperation, fileId, parentBucketPointer.getPageIndex(), false); + + parentCacheEntry.acquireExclusiveLock(); + + insertionIndex = bucketSearchResult.itemIndex; + + parentBucket = new OSBTreeBonsaiBucket(parentCacheEntry, parentBucketPointer.getPageOffset(), keySerializer, + valueSerializer, getChanges(atomicOperation, parentCacheEntry), this); + } + + } finally { + parentCacheEntry.releaseExclusiveLock(); + + releasePage(atomicOperation, parentCacheEntry); + } + + } finally { + rightBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightBucketEntry); + } + + ArrayList resultPath = new ArrayList(path.subList(0, path.size() - 1)); + + if (comparator.compare(keyToInsert, separationKey) < 0) { + resultPath.add(bucketPointer); + return new BucketSearchResult(keyIndex, resultPath); + } + + resultPath.add(rightBucketPointer); + if (splitLeaf) { + return new BucketSearchResult(keyIndex - indexToSplit, resultPath); + } + return new BucketSearchResult(keyIndex - indexToSplit - 1, resultPath); + + } else { + long treeSize = bucketToSplit.getTreeSize(); + + final List> leftEntries = new ArrayList>( + indexToSplit); + + for (int i = 0; i < indexToSplit; i++) + leftEntries.add(bucketToSplit.getEntry(i)); + + final AllocationResult leftAllocationResult = allocateBucket(atomicOperation); + OCacheEntry leftBucketEntry = leftAllocationResult.getCacheEntry(); + OBonsaiBucketPointer leftBucketPointer = leftAllocationResult.getPointer(); + + final AllocationResult rightAllocationResult = allocateBucket(atomicOperation); + OCacheEntry rightBucketEntry = rightAllocationResult.getCacheEntry(); + OBonsaiBucketPointer rightBucketPointer = rightAllocationResult.getPointer(); + leftBucketEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket newLeftBucket = new OSBTreeBonsaiBucket(leftBucketEntry, + leftBucketPointer.getPageOffset(), splitLeaf, keySerializer, valueSerializer, + getChanges(atomicOperation, leftBucketEntry), this); + newLeftBucket.addAll(leftEntries); + + if (splitLeaf) + newLeftBucket.setRightSibling(rightBucketPointer); + } finally { + leftBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, leftBucketEntry); + } + + rightBucketEntry.acquireExclusiveLock(); + try { + OSBTreeBonsaiBucket newRightBucket = new OSBTreeBonsaiBucket(rightBucketEntry, + rightBucketPointer.getPageOffset(), splitLeaf, keySerializer, valueSerializer, + getChanges(atomicOperation, rightBucketEntry), this); + newRightBucket.addAll(rightEntries); + + if (splitLeaf) + newRightBucket.setLeftSibling(leftBucketPointer); + } finally { + rightBucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, rightBucketEntry); + } + + bucketToSplit = new OSBTreeBonsaiBucket(bucketEntry, bucketPointer.getPageOffset(), false, keySerializer, + valueSerializer, getChanges(atomicOperation, bucketEntry), this); + bucketToSplit.setTreeSize(treeSize); + + bucketToSplit + .addEntry(0, new OSBTreeBonsaiBucket.SBTreeEntry(leftBucketPointer, rightBucketPointer, separationKey, null), + true); + + ArrayList resultPath = new ArrayList(path.subList(0, path.size() - 1)); + + if (comparator.compare(keyToInsert, separationKey) < 0) { + resultPath.add(leftBucketPointer); + return new BucketSearchResult(keyIndex, resultPath); + } + + resultPath.add(rightBucketPointer); + + if (splitLeaf) + return new BucketSearchResult(keyIndex - indexToSplit, resultPath); + + return new BucketSearchResult(keyIndex - indexToSplit - 1, resultPath); + } + + } finally { + bucketEntry.releaseExclusiveLock(); + releasePage(atomicOperation, bucketEntry); + } + } + + private BucketSearchResult findBucket(K key, OAtomicOperation atomicOperation) throws IOException { + OBonsaiBucketPointer bucketPointer = rootBucketPointer; + final ArrayList path = new ArrayList(); + + while (true) { + path.add(bucketPointer); + final OCacheEntry bucketEntry = loadPage(atomicOperation, fileId, bucketPointer.getPageIndex(), false); + bucketEntry.acquireSharedLock(); + + final OSBTreeBonsaiBucket.SBTreeEntry entry; + try { + final OSBTreeBonsaiBucket keyBucket = new OSBTreeBonsaiBucket(bucketEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, bucketEntry), this); + final int index = keyBucket.find(key); + + if (keyBucket.isLeaf()) + return new BucketSearchResult(index, path); + + if (index >= 0) + entry = keyBucket.getEntry(index); + else { + final int insertionIndex = -index - 1; + if (insertionIndex >= keyBucket.size()) + entry = keyBucket.getEntry(insertionIndex - 1); + else + entry = keyBucket.getEntry(insertionIndex); + } + + } finally { + bucketEntry.releaseSharedLock(); + releasePage(atomicOperation, bucketEntry); + } + + if (comparator.compare(key, entry.key) >= 0) + bucketPointer = entry.rightChild; + else + bucketPointer = entry.leftChild; + } + } + + private void initSysBucket(OAtomicOperation atomicOperation) throws IOException { + OCacheEntry sysCacheEntry = loadPage(atomicOperation, fileId, SYS_BUCKET.getPageIndex(), false); + if (sysCacheEntry == null) { + sysCacheEntry = addPage(atomicOperation, fileId); + assert sysCacheEntry.getPageIndex() == SYS_BUCKET.getPageIndex(); + } + + sysCacheEntry.acquireExclusiveLock(); + try { + OSysBucket sysBucket = new OSysBucket(sysCacheEntry, getChanges(atomicOperation, sysCacheEntry)); + if (sysBucket.isInitialized()) { + sysCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, sysCacheEntry); + + sysCacheEntry = loadPage(atomicOperation, fileId, SYS_BUCKET.getPageIndex(), false); + sysCacheEntry.acquireExclusiveLock(); + + sysBucket = new OSysBucket(sysCacheEntry, getChanges(atomicOperation, sysCacheEntry)); + sysBucket.init(); + } + } finally { + sysCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, sysCacheEntry); + } + } + + private AllocationResult allocateBucket(OAtomicOperation atomicOperation) throws IOException { + OCacheEntry sysCacheEntry = loadPage(atomicOperation, fileId, SYS_BUCKET.getPageIndex(), false); + if (sysCacheEntry == null) { + sysCacheEntry = addPage(atomicOperation, fileId); + assert sysCacheEntry.getPageIndex() == SYS_BUCKET.getPageIndex(); + } + + sysCacheEntry.acquireExclusiveLock(); + try { + final OSysBucket sysBucket = new OSysBucket(sysCacheEntry, getChanges(atomicOperation, sysCacheEntry)); + if ((1.0 * sysBucket.freeListLength()) / ((1.0 * getFilledUpTo(atomicOperation, fileId)) * PAGE_SIZE + / OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES) >= freeSpaceReuseTrigger) { + final AllocationResult allocationResult = reuseBucketFromFreeList(sysBucket, atomicOperation); + return allocationResult; + } else { + final OBonsaiBucketPointer freeSpacePointer = sysBucket.getFreeSpacePointer(); + if (freeSpacePointer.getPageOffset() + OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES > PAGE_SIZE) { + final OCacheEntry cacheEntry = addPage(atomicOperation, fileId); + final long pageIndex = cacheEntry.getPageIndex(); + sysBucket.setFreeSpacePointer(new OBonsaiBucketPointer(pageIndex, OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES)); + + return new AllocationResult(new OBonsaiBucketPointer(pageIndex, 0), cacheEntry); + } else { + sysBucket.setFreeSpacePointer(new OBonsaiBucketPointer(freeSpacePointer.getPageIndex(), + freeSpacePointer.getPageOffset() + OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES)); + final OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, freeSpacePointer.getPageIndex(), false); + + return new AllocationResult(freeSpacePointer, cacheEntry); + } + } + } finally { + sysCacheEntry.releaseExclusiveLock(); + releasePage(atomicOperation, sysCacheEntry); + } + } + + private AllocationResult reuseBucketFromFreeList(OSysBucket sysBucket, OAtomicOperation atomicOperation) throws IOException { + final OBonsaiBucketPointer oldFreeListHead = sysBucket.getFreeListHead(); + assert oldFreeListHead.isValid(); + + OCacheEntry cacheEntry = loadPage(atomicOperation, fileId, oldFreeListHead.getPageIndex(), false); + cacheEntry.acquireExclusiveLock(); + try { + final OSBTreeBonsaiBucket bucket = new OSBTreeBonsaiBucket(cacheEntry, oldFreeListHead.getPageOffset(), + keySerializer, valueSerializer, getChanges(atomicOperation, cacheEntry), this); + + sysBucket.setFreeListHead(bucket.getFreeListPointer()); + sysBucket.setFreeListLength(sysBucket.freeListLength() - 1); + } finally { + cacheEntry.releaseExclusiveLock(); + } + return new AllocationResult(oldFreeListHead, cacheEntry); + } + + @Override + public int getRealBagSize(Map changes) { + startOperation(); + try { + final Map notAppliedChanges = new HashMap(changes); + final OModifiableInteger size = new OModifiableInteger(0); + loadEntriesMajor(firstKey(), true, true, new RangeResultListener() { + @Override + public boolean addResult(Map.Entry entry) { + final OSBTreeRidBag.Change change = notAppliedChanges.remove(entry.getKey()); + final int result; + + final Integer treeValue = (Integer) entry.getValue(); + if (change == null) + result = treeValue; + else + result = change.applyTo(treeValue); + + size.increment(result); + return true; + } + }); + + for (OSBTreeRidBag.Change change : notAppliedChanges.values()) { + size.increment(change.applyTo(0)); + } + + return size.intValue(); + } finally { + completeOperation(); + } + } + + @Override + public OBinarySerializer getKeySerializer() { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + return keySerializer; + } finally { + lock.unlock(); + } + } + + @Override + public OBinarySerializer getValueSerializer() { + final Lock lock = FILE_LOCK_MANAGER.acquireSharedLock(fileId); + try { + return valueSerializer; + } finally { + lock.unlock(); + } + } + + private static class AllocationResult { + private final OBonsaiBucketPointer pointer; + private final OCacheEntry cacheEntry; + + private AllocationResult(OBonsaiBucketPointer pointer, OCacheEntry cacheEntry) { + this.pointer = pointer; + this.cacheEntry = cacheEntry; + } + + private OBonsaiBucketPointer getPointer() { + return pointer; + } + + private OCacheEntry getCacheEntry() { + return cacheEntry; + } + } + + private static class BucketSearchResult { + private final int itemIndex; + private final ArrayList path; + + private BucketSearchResult(int itemIndex, ArrayList path) { + this.itemIndex = itemIndex; + this.path = path; + } + + public OBonsaiBucketPointer getLastPathItem() { + return path.get(path.size() - 1); + } + } + + private static final class PagePathItemUnit { + private final OBonsaiBucketPointer bucketPointer; + private final int itemIndex; + + private PagePathItemUnit(OBonsaiBucketPointer bucketPointer, int itemIndex) { + this.bucketPointer = bucketPointer; + this.itemIndex = itemIndex; + } + } + + public void debugPrintBucket(PrintStream writer) throws IOException { + final ArrayList path = new ArrayList(); + path.add(rootBucketPointer); + debugPrintBucket(rootBucketPointer, writer, path); + } + + public void debugPrintBucket(OBonsaiBucketPointer bucketPointer, PrintStream writer, final ArrayList path) + throws IOException { + + final OCacheEntry bucketEntry = loadPage(null, fileId, bucketPointer.getPageIndex(), false); + bucketEntry.acquireSharedLock(); + OSBTreeBonsaiBucket.SBTreeEntry entry; + try { + final OSBTreeBonsaiBucket keyBucket = new OSBTreeBonsaiBucket(bucketEntry, bucketPointer.getPageOffset(), + keySerializer, valueSerializer, null, this); + if (keyBucket.isLeaf()) { + for (int i = 0; i < path.size(); i++) + writer.append("\t"); + writer.append(" Leaf backet:" + bucketPointer.getPageIndex() + "|" + bucketPointer.getPageOffset()); + writer + .append(" left bucket:" + keyBucket.getLeftSibling().getPageIndex() + "|" + keyBucket.getLeftSibling().getPageOffset()); + writer.append( + " right bucket:" + keyBucket.getRightSibling().getPageIndex() + "|" + keyBucket.getRightSibling().getPageOffset()); + writer.append(" size:" + keyBucket.size()); + writer.append(" content: ["); + for (int index = 0; index < keyBucket.size(); index++) { + entry = keyBucket.getEntry(index); + writer.append(entry.getKey() + ","); + } + writer.append("\n"); + } else { + for (int i = 0; i < path.size(); i++) + writer.append("\t"); + writer.append(" node bucket:" + bucketPointer.getPageIndex() + "|" + bucketPointer.getPageOffset()); + writer + .append(" left bucket:" + keyBucket.getLeftSibling().getPageIndex() + "|" + keyBucket.getLeftSibling().getPageOffset()); + writer.append( + " right bucket:" + keyBucket.getRightSibling().getPageIndex() + "|" + keyBucket.getRightSibling().getPageOffset()); + writer.append("\n"); + for (int index = 0; index < keyBucket.size(); index++) { + entry = keyBucket.getEntry(index); + for (int i = 0; i < path.size(); i++) + writer.append("\t"); + writer.append(" entry:" + index + " key: " + entry.getKey() + " left \n"); + OBonsaiBucketPointer next = entry.leftChild; + path.add(next); + debugPrintBucket(next, writer, path); + path.remove(next); + for (int i = 0; i < path.size(); i++) + writer.append("\t"); + writer.append(" entry:" + index + " key: " + entry.getKey() + " right \n"); + next = entry.rightChild; + path.add(next); + debugPrintBucket(next, writer, path); + path.remove(next); + + } + } + } finally { + bucketEntry.releaseSharedLock(); + releasePage(null, bucketEntry); + } + + } + + @Override + protected void startOperation() { + OSessionStoragePerformanceStatistic sessionStoragePerformanceStatistic = performanceStatisticManager + .getSessionPerformanceStatistic(); + if (sessionStoragePerformanceStatistic != null) { + sessionStoragePerformanceStatistic + .startComponentOperation(getFullName(), OSessionStoragePerformanceStatistic.ComponentType.RIDBAG); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSysBucket.java b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSysBucket.java new file mode 100755 index 00000000000..f6e72f074f5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/index/sbtreebonsai/local/OSysBucket.java @@ -0,0 +1,94 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.index.sbtreebonsai.local; + +import com.orientechnologies.common.serialization.types.OByteSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.storage.cache.OCacheEntry; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; + +/** + *

          + * A system bucket for bonsai tree pages. Single per file. + *

          + *

          + * Holds an information about: + *

          + *
            + *
          • head of free list
          • + *
          • length of free list
          • + *
          • pointer to free space
          • + *
          + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OSysBucket extends OBonsaiBucketAbstract { + private static final int SYS_MAGIC_OFFSET = WAL_POSITION_OFFSET + OLongSerializer.LONG_SIZE; + private static final int FREE_SPACE_OFFSET = SYS_MAGIC_OFFSET + OByteSerializer.BYTE_SIZE; + private static final int FREE_LIST_HEAD_OFFSET = FREE_SPACE_OFFSET + OBonsaiBucketPointer.SIZE; + private static final int FREE_LIST_LENGTH_OFFSET = FREE_LIST_HEAD_OFFSET + OBonsaiBucketPointer.SIZE; + + /** + * Magic number to check if the sys bucket is initialized. + */ + private static final byte SYS_MAGIC = (byte) 41; + + public OSysBucket(OCacheEntry cacheEntry, OWALChanges changes) { + super(cacheEntry, changes); + } + + public void init() throws IOException { + setByteValue(SYS_MAGIC_OFFSET, SYS_MAGIC); + setBucketPointer(FREE_SPACE_OFFSET, new OBonsaiBucketPointer(0, OSBTreeBonsaiBucket.MAX_BUCKET_SIZE_BYTES)); + setBucketPointer(FREE_LIST_HEAD_OFFSET, OBonsaiBucketPointer.NULL); + setLongValue(FREE_LIST_LENGTH_OFFSET, 0L); + } + + public boolean isInitialized() { + return getByteValue(SYS_MAGIC_OFFSET) != 41; + } + + public long freeListLength() { + return getLongValue(FREE_LIST_LENGTH_OFFSET); + } + + public void setFreeListLength(long length) throws IOException { + setLongValue(FREE_LIST_LENGTH_OFFSET, length); + } + + public OBonsaiBucketPointer getFreeSpacePointer() { + return getBucketPointer(FREE_SPACE_OFFSET); + } + + public void setFreeSpacePointer(OBonsaiBucketPointer pointer) throws IOException { + setBucketPointer(FREE_SPACE_OFFSET, pointer); + } + + public OBonsaiBucketPointer getFreeListHead() { + return getBucketPointer(FREE_LIST_HEAD_OFFSET); + } + + public void setFreeListHead(OBonsaiBucketPointer pointer) throws IOException { + setBucketPointer(FREE_LIST_HEAD_OFFSET, pointer); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/intent/OIntent.java b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntent.java new file mode 100644 index 00000000000..5a12af973ba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntent.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.intent; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; + +/** + * Intents aim to define common use case in order to optimize the execution. + * + * @author Luca Garulli + * + */ +public interface OIntent { + /** + * Activate the intent. + * + * @param iDatabase + * Database where to activate it + */ + public void begin(ODatabaseDocumentInternal iDatabase); + + /** + * Activate the intent. + * + * @param iDatabase + * Database where to activate it + */ + public void end(ODatabaseDocumentInternal iDatabase); + + public OIntent copy(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveInsert.java b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveInsert.java new file mode 100644 index 00000000000..9425bef5503 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveInsert.java @@ -0,0 +1,181 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.intent; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.index.OClassIndexManager; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; + +import java.util.HashMap; +import java.util.Map; + +public class OIntentMassiveInsert implements OIntent { + private boolean previousRetainRecords; + private boolean previousRetainObjects; + private boolean previousValidation; + private boolean previousTxRequiredForSQLGraphOperations; + private Map removedHooks; + private OSecurityUser currentUser; + private boolean disableValidation = true; + private boolean disableSecurity = true; + private boolean disableHooks = true; + private boolean enableCache = true; + + public void begin(final ODatabaseDocumentInternal iDatabase) { + if (disableSecurity) { + // DISABLE CHECK OF SECURITY + currentUser = iDatabase.getDatabaseOwner().getUser(); + iDatabase.getDatabaseOwner().setUser(null); + } + ODatabaseInternal ownerDb = iDatabase.getDatabaseOwner(); + + // DISABLE TX IN GRAPH SQL OPERATIONS + previousTxRequiredForSQLGraphOperations = ownerDb.getStorage().getConfiguration().isTxRequiredForSQLGraphOperations(); + if (previousTxRequiredForSQLGraphOperations) + ownerDb.getStorage().getConfiguration().setProperty("txRequiredForSQLGraphOperations", Boolean.FALSE.toString()); + + if (!enableCache) { + ownerDb.getLocalCache().setEnable(enableCache); + } + + if (ownerDb instanceof ODatabaseDocument) { + previousRetainRecords = ((ODatabaseDocument) ownerDb).isRetainRecords(); + ((ODatabaseDocument) ownerDb).setRetainRecords(false); + + // VALIDATION + if (disableValidation && !iDatabase.getStorage().isRemote() ) { + // Avoid to change server side validation if massive intent run on a client + previousValidation = ((ODatabaseDocument) ownerDb).isValidationEnabled(); + if (previousValidation) + ((ODatabaseDocument) ownerDb).setValidationEnabled(false); + } + } + + while (ownerDb.getDatabaseOwner() != ownerDb) + ownerDb = ownerDb.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseObject) { + previousRetainObjects = ((ODatabaseObject) ownerDb).isRetainObjects(); + ((ODatabaseObject) ownerDb).setRetainObjects(false); + } + + if (disableHooks) { + // REMOVE ALL HOOKS BUT INDEX + removedHooks = new HashMap(); + HashMap hooks = new HashMap( + ownerDb.getHooks()); + for (Map.Entry hook : hooks.entrySet()) { + if (!(hook.getKey() instanceof OClassIndexManager)) { + removedHooks.put(hook.getKey(), hook.getValue()); + ownerDb.unregisterHook(hook.getKey()); + } + } + } + } + + public void end(final ODatabaseDocumentInternal iDatabase) { + ODatabaseInternal ownerDb = iDatabase.getDatabaseOwner(); + + if (disableSecurity) + if (currentUser != null) + // RE-ENABLE CHECK OF SECURITY + ownerDb.setUser(currentUser); + + if (previousTxRequiredForSQLGraphOperations) + ownerDb.getStorage().getConfiguration().setProperty("txRequiredForSQLGraphOperations", Boolean.TRUE.toString()); + + if (!enableCache) { + ownerDb.getLocalCache().setEnable(!enableCache); + } + if (ownerDb instanceof ODatabaseDocument) { + ((ODatabaseDocument) ownerDb).setRetainRecords(previousRetainRecords); + if (disableValidation && !iDatabase.getStorage().isRemote()) + ((ODatabaseDocument) ownerDb).setValidationEnabled(previousValidation); + } + + while (ownerDb.getDatabaseOwner() != ownerDb) + ownerDb = ownerDb.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseObject) + ((ODatabaseObject) ownerDb).setRetainObjects(previousRetainObjects); + + if (disableHooks) + if (removedHooks != null) { + // RESTORE ALL REMOVED HOOKS + for (Map.Entry hook : removedHooks.entrySet()) { + ownerDb.registerHook(hook.getKey(), hook.getValue()); + } + } + } + + public boolean isDisableValidation() { + return disableValidation; + } + + public OIntentMassiveInsert setDisableValidation(final boolean disableValidation) { + this.disableValidation = disableValidation; + return this; + } + + public boolean isDisableSecurity() { + return disableSecurity; + } + + public OIntentMassiveInsert setDisableSecurity(final boolean disableSecurity) { + this.disableSecurity = disableSecurity; + return this; + } + + public boolean isDisableHooks() { + return disableHooks; + } + + public OIntentMassiveInsert setDisableHooks(final boolean disableHooks) { + this.disableHooks = disableHooks; + return this; + + } + + public OIntentMassiveInsert setEnableCache(boolean enableCache) { + this.enableCache = enableCache; + return this; + + } + + @Override + public OIntent copy() { + final OIntentMassiveInsert copy = new OIntentMassiveInsert(); + copy.previousRetainRecords = previousRetainRecords; + copy.previousRetainObjects = previousRetainObjects; + copy.previousValidation = previousValidation; + copy.disableValidation = disableValidation; + copy.disableSecurity = disableSecurity; + copy.disableHooks = disableHooks; + copy.currentUser = currentUser; + if (removedHooks != null) + copy.removedHooks = new HashMap(removedHooks); + return copy; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveRead.java b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveRead.java new file mode 100644 index 00000000000..4192ff6474f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentMassiveRead.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.intent; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; + +public class OIntentMassiveRead implements OIntent { + public void begin(final ODatabaseDocumentInternal iDatabase) { + } + + public void end(final ODatabaseDocumentInternal iDatabase) { + } + + @Override + public OIntent copy() { + final OIntentMassiveRead copy = new OIntentMassiveRead(); + return copy; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentNoCache.java b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentNoCache.java new file mode 100644 index 00000000000..78711728532 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/intent/OIntentNoCache.java @@ -0,0 +1,75 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.intent; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; + +/** + * Disable cache. This is helpful with operation like UPDATE/DELETE of many records. + */ +public class OIntentNoCache implements OIntent { + private boolean previousLocalCacheEnabled; + private boolean previousRetainRecords; + private boolean previousRetainObjects; + + public void begin(final ODatabaseDocumentInternal iDatabase) { + ODatabaseInternal ownerDb = iDatabase.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseDocument) { + previousRetainRecords = ((ODatabaseDocument) ownerDb).isRetainRecords(); + ((ODatabaseDocument) ownerDb).setRetainRecords(false); + } + + while (ownerDb.getDatabaseOwner() != ownerDb) + ownerDb = ownerDb.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseObject) { + previousRetainObjects = ((ODatabaseObject) ownerDb).isRetainObjects(); + ((ODatabaseObject) ownerDb).setRetainObjects(false); + } + } + + public void end(final ODatabaseDocumentInternal iDatabase) { + ODatabaseInternal ownerDb = iDatabase.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseDocument) { + ((ODatabaseDocument) ownerDb).setRetainRecords(previousRetainRecords); + } + + while (ownerDb.getDatabaseOwner() != ownerDb) + ownerDb = ownerDb.getDatabaseOwner(); + + if (ownerDb instanceof ODatabaseObject) + ((ODatabaseObject) ownerDb).setRetainObjects(previousRetainObjects); + } + + @Override + public OIntent copy() { + final OIntentNoCache copy = new OIntentNoCache(); + copy.previousLocalCacheEnabled = previousLocalCacheEnabled; + copy.previousRetainRecords = previousRetainRecords; + copy.previousRetainObjects = previousRetainObjects; + return copy; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyIterator.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyIterator.java new file mode 100644 index 00000000000..b440c3bac34 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyIterator.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Iterator; + +/** + * Empty iterator against Object. + */ +public class OEmptyIterator implements Iterator { + public static final OEmptyIterator ANY_INSTANCE = new OEmptyIterator(); + public static final OEmptyIterator IDENTIFIABLE_INSTANCE = new OEmptyIterator(); + + public boolean hasNext() { + return false; + } + + public T next() { + return null; + } + + public void remove() { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyMapEntryIterator.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyMapEntryIterator.java new file mode 100644 index 00000000000..230dbccf274 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/OEmptyMapEntryIterator.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import java.util.Iterator; +import java.util.Map; + +public class OEmptyMapEntryIterator implements Iterator> { + public static final OEmptyMapEntryIterator INSTANCE = new OEmptyMapEntryIterator(); + + public boolean hasNext() { + return false; + } + + public Map.Entry next() { + return null; + } + + public void remove() { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/OIdentifiableIterator.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/OIdentifiableIterator.java new file mode 100755 index 00000000000..e777102aa95 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/OIdentifiableIterator.java @@ -0,0 +1,441 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import java.util.*; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.OPhysicalPosition; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.record.ORecordVersionHelper; + +/** + * Iterator class to browse forward and backward the records of a cluster. Once browsed in a direction, the iterator cannot change + * it. + * + * @author Luca Garulli + */ +public abstract class OIdentifiableIterator implements Iterator, Iterable { + protected final ODatabaseDocumentInternal database; + protected final ORecordId current = new ORecordId(); + private final ODatabaseDocumentInternal lowLevelDatabase; + private final OStorage dbStorage; + private final boolean iterateThroughTombstones; + protected boolean liveUpdated = false; + protected long limit = -1; + protected long browsedRecords = 0; + protected OStorage.LOCKING_STRATEGY lockingStrategy = OStorage.LOCKING_STRATEGY.NONE; + protected long totalAvailableRecords; + protected List txEntries; + protected int currentTxEntryPosition = -1; + protected long firstClusterEntry = 0; + protected long lastClusterEntry = Long.MAX_VALUE; + private String fetchPlan; + private ORecord reusedRecord = null; // DEFAULT = NOT + // REUSE IT + private Boolean directionForward; + private long currentEntry = ORID.CLUSTER_POS_INVALID; + private int currentEntryPosition = -1; + private OPhysicalPosition[] positionsToProcess = null; + + /** + * Set of RIDs of records which were indicated as broken during cluster iteration. + * Mainly used during JSON export/import procedure to fix links on broken records. + */ + final Set brokenRIDs = new HashSet(); + + public OIdentifiableIterator(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase) { + this(iDatabase, iLowLevelDatabase, false, OStorage.LOCKING_STRATEGY.NONE); + } + + /** + * @deprecated usage of this constructor may lead to deadlocks. + */ + @Deprecated + public OIdentifiableIterator(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final boolean iterateThroughTombstones, final OStorage.LOCKING_STRATEGY iLockingStrategy) { + database = iDatabase; + lowLevelDatabase = iLowLevelDatabase; + this.iterateThroughTombstones = iterateThroughTombstones; + lockingStrategy = iLockingStrategy; + + dbStorage = lowLevelDatabase.getStorage(); + current.setClusterPosition(ORID.CLUSTER_POS_INVALID); // DEFAULT = START FROM THE BEGIN + } + + public boolean isIterateThroughTombstones() { + return iterateThroughTombstones; + } + + public abstract boolean hasPrevious(); + + public abstract OIdentifiable previous(); + + public abstract OIdentifiableIterator begin(); + + public abstract OIdentifiableIterator last(); + + public ORecord current() { + return readCurrentRecord(getRecord(), 0); + } + + public String getFetchPlan() { + return fetchPlan; + } + + public void setFetchPlan(String fetchPlan) { + this.fetchPlan = fetchPlan; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + /** + * Tells if the iterator is using the same record for browsing. + * + * @see #setReuseSameRecord(boolean) + */ + public boolean isReuseSameRecord() { + return reusedRecord != null; + } + + /** + * Tell to the iterator to use the same record for browsing. The record will be reset before every use. This improve the + * performance and reduce memory utilization since it does not create a new one for each operation, but pay attention to copy the + * data of the record once read otherwise they will be reset to the next operation. + * + * @param reuseSameRecord if true the same record will be used for iteration. If false new record will be created each time + * iterator retrieves record from db. + * + * @return @see #isReuseSameRecord() + */ + public OIdentifiableIterator setReuseSameRecord(final boolean reuseSameRecord) { + reusedRecord = (ORecord) (reuseSameRecord ? database.newInstance() : null); + return this; + } + + public long getCurrentEntry() { + return currentEntry; + } + + /** + * Return the iterator to be used in Java5+ constructs
          + *
          + * + * for( ORecordDocument rec : database.browseCluster( "Animal" ) ){
          + * ...
          + * }
          + *
          + */ + public Iterator iterator() { + return this; + } + + /** + * Return the current limit on browsing record. -1 means no limits (default). + * + * @return The limit if setted, otherwise -1 + * + * @see #setLimit(long) + */ + public long getLimit() { + return limit; + } + + /** + * Set the limit on browsing record. -1 means no limits. You can set the limit even while you're browsing. + * + * @param limit The current limit on browsing record. -1 means no limits (default). + * + * @see #getLimit() + */ + public OIdentifiableIterator setLimit(final long limit) { + this.limit = limit; + return this; + } + + public Set getBrokenRIDs() { + return brokenRIDs; + } + + /** + * Return current configuration of live updates. + * + * @return True to activate it, otherwise false (default) + * + * @see #setLiveUpdated(boolean) + */ + public boolean isLiveUpdated() { + return liveUpdated; + } + + /** + * Tell to the iterator that the upper limit must be checked at every cycle. Useful when concurrent deletes or additions change + * the size of the cluster while you're browsing it. Default is false. + * + * @param liveUpdated True to activate it, otherwise false (default) + * + * @see #isLiveUpdated() + */ + public OIdentifiableIterator setLiveUpdated(final boolean liveUpdated) { + this.liveUpdated = liveUpdated; + return this; + } + + protected ORecord getTransactionEntry() { + boolean noPhysicalRecordToBrowse; + + if (current.getClusterPosition() < ORID.CLUSTER_POS_INVALID) + noPhysicalRecordToBrowse = true; + else if (directionForward) + noPhysicalRecordToBrowse = lastClusterEntry <= currentEntry; + else + noPhysicalRecordToBrowse = currentEntry <= firstClusterEntry; + + if (!noPhysicalRecordToBrowse && positionsToProcess.length == 0) + noPhysicalRecordToBrowse = true; + + if (noPhysicalRecordToBrowse && txEntries != null) { + // IN TX + currentTxEntryPosition++; + if (currentTxEntryPosition >= txEntries.size()) + throw new NoSuchElementException(); + else + return txEntries.get(currentTxEntryPosition).getRecord(); + } + return null; + } + + /** + * Return the record to use for the operation. + * + * @return the record to use for the operation. + */ + protected ORecord getRecord() { + final ORecord record; + if (reusedRecord != null) { + // REUSE THE SAME RECORD AFTER HAVING RESETTED IT + record = reusedRecord; + record.reset(); + } else + record = null; + return record; + } + + protected void checkDirection(final boolean iForward) { + if (directionForward == null) + // SET THE DIRECTION + directionForward = iForward; + else if (directionForward != iForward) + throw new OIterationException("Iterator cannot change direction while browsing"); + } + + /** + * Read the current record and increment the counter if the record was found. + * + * @param iRecord to read value from database inside it. If record is null link will be created and stored in it. + * + * @return record which was read from db. + */ + protected ORecord readCurrentRecord(ORecord iRecord, final int iMovement) { + if (limit > -1 && browsedRecords >= limit) + // LIMIT REACHED + return null; + + do { + final boolean moveResult; + switch (iMovement) { + case 1: + moveResult = nextPosition(); + break; + case -1: + moveResult = prevPosition(); + break; + case 0: + moveResult = checkCurrentPosition(); + break; + default: + throw new IllegalStateException("Invalid movement value : " + iMovement); + } + + if (!moveResult) + return null; + + try { + if (iRecord != null) { + ORecordInternal.setIdentity(iRecord, new ORecordId(current.getClusterId(), current.getClusterPosition())); + iRecord = lowLevelDatabase.load(iRecord, fetchPlan, false, true, iterateThroughTombstones, lockingStrategy); + } else + iRecord = lowLevelDatabase.load(current, fetchPlan, false, true, iterateThroughTombstones, lockingStrategy); + } catch (ODatabaseException e) { + if (Thread.interrupted() || lowLevelDatabase.isClosed()) + // THREAD INTERRUPTED: RETURN + throw e; + + if (e.getCause() instanceof OSecurityException) + throw e; + + brokenRIDs.add(current.copy()); + + OLogManager.instance().error(this, "Error on fetching record during browsing. The record has been skipped", e); + } + + if (iRecord != null) { + browsedRecords++; + return iRecord; + } + } while (iMovement != 0); + + return null; + } + + protected boolean nextPosition() { + if (positionsToProcess == null) { + positionsToProcess = dbStorage.ceilingPhysicalPositions(current.getClusterId(), new OPhysicalPosition(firstClusterEntry)); + if (positionsToProcess == null) + return false; + } else { + if (currentEntry >= lastClusterEntry) + return false; + } + + incrementEntreePosition(); + while (positionsToProcess.length > 0 && currentEntryPosition >= positionsToProcess.length) { + positionsToProcess = dbStorage + .higherPhysicalPositions(current.getClusterId(), positionsToProcess[positionsToProcess.length - 1]); + + currentEntryPosition = -1; + incrementEntreePosition(); + } + + if (positionsToProcess.length == 0) + return false; + + currentEntry = positionsToProcess[currentEntryPosition].clusterPosition; + + if (currentEntry > lastClusterEntry || currentEntry == ORID.CLUSTER_POS_INVALID) + return false; + + current.setClusterPosition(currentEntry); + return true; + } + + protected boolean checkCurrentPosition() { + if (currentEntry == ORID.CLUSTER_POS_INVALID || firstClusterEntry > currentEntry || lastClusterEntry < currentEntry) + return false; + + current.setClusterPosition(currentEntry); + return true; + } + + protected boolean prevPosition() { + if (positionsToProcess == null) { + positionsToProcess = dbStorage.floorPhysicalPositions(current.getClusterId(), new OPhysicalPosition(lastClusterEntry)); + if (positionsToProcess == null) + return false; + + if (positionsToProcess.length == 0) + return false; + + currentEntryPosition = positionsToProcess.length; + } else { + if (currentEntry < firstClusterEntry) + return false; + } + + decrementEntreePosition(); + + while (positionsToProcess.length > 0 && currentEntryPosition < 0) { + positionsToProcess = dbStorage.lowerPhysicalPositions(current.getClusterId(), positionsToProcess[0]); + currentEntryPosition = positionsToProcess.length; + + decrementEntreePosition(); + } + + if (positionsToProcess.length == 0) + return false; + + currentEntry = positionsToProcess[currentEntryPosition].clusterPosition; + + if (currentEntry < firstClusterEntry) + return false; + + current.setClusterPosition(currentEntry); + return true; + } + + protected void resetCurrentPosition() { + currentEntry = ORID.CLUSTER_POS_INVALID; + positionsToProcess = null; + currentEntryPosition = -1; + } + + protected long currentPosition() { + return currentEntry; + } + + protected void checkForSystemClusters(final ODatabaseDocumentInternal iDatabase, final int[] iClusterIds) { + for (int clId : iClusterIds) { + final OCluster cl = iDatabase.getStorage().getClusterById(clId); + if (cl != null && cl.isSystemCluster()) { + final OSecurityUser dbUser = iDatabase.getUser(); + if (dbUser == null || dbUser.allow(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_READ) != null) + // AUTHORIZED + break; + } + } + } + + private void decrementEntreePosition() { + if (positionsToProcess.length > 0) + if (iterateThroughTombstones) + currentEntryPosition--; + else + do { + currentEntryPosition--; + } while (currentEntryPosition >= 0 && ORecordVersionHelper + .isTombstone(positionsToProcess[currentEntryPosition].recordVersion)); + } + + private void incrementEntreePosition() { + if (positionsToProcess.length > 0) + if (iterateThroughTombstones) + currentEntryPosition++; + else + do { + currentEntryPosition++; + } while (currentEntryPosition < positionsToProcess.length && ORecordVersionHelper + .isTombstone(positionsToProcess[currentEntryPosition].recordVersion)); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/OIterationException.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/OIterationException.java new file mode 100755 index 00000000000..829d32e6a39 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/OIterationException.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; + +public class OIterationException extends OSystemException { + + private static final long serialVersionUID = 2347493191705052402L; + + public OIterationException(OIterationException exception) { + super(exception); + } + + public OIterationException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/OLazyWrapperIterator.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/OLazyWrapperIterator.java new file mode 100644 index 00000000000..1e4552ffa33 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/OLazyWrapperIterator.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.db.record.OAutoConvertToRecord; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Iterator that created wrapped objects during browsing. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public abstract class OLazyWrapperIterator implements OAutoConvertToRecord, Iterator, Iterable, OResettable, OSizeable { + protected final Iterator iterator; + protected OIdentifiable nextRecord; + protected T nextElement; + protected final int size; // -1 = UNKNOWN + protected boolean autoConvertToRecord = true; + protected Object multiValue; + + public OLazyWrapperIterator(final Iterator iterator) { + this.iterator = iterator; + this.size = -1; + } + + public OLazyWrapperIterator(final Iterator iterator, final int iSize, final Object iOriginalValue) { + this.iterator = iterator; + this.size = iSize; + this.multiValue = iOriginalValue; + } + + public abstract boolean filter(T iObject); + + public abstract boolean canUseMultiValueDirectly(); + + public abstract T createGraphElement(Object iObject); + + public OIdentifiable getGraphElementRecord(final Object iObject) { + return (OIdentifiable) iObject; + } + + @Override + public Iterator iterator() { + reset(); + return this; + } + + public int size() { + if (size > -1) + return size; + + if (iterator instanceof OSizeable) + return ((OSizeable) iterator).size(); + + return 0; + } + + @Override + public void reset() { + if (iterator instanceof OResettable) + // RESET IT FOR MULTIPLE ITERATIONS + ((OResettable) iterator).reset(); + nextElement = null; + } + + @Override + public boolean hasNext() { + if (autoConvertToRecord) { + // ACT ON WRAPPER + while (nextElement == null && iterator.hasNext()) { + nextElement = createGraphElement(iterator.next()); + if (nextElement != null && !filter(nextElement)) + nextElement = null; + } + + return nextElement != null; + } + + // ACT ON RECORDS (FASTER & LIGHTER) + while (nextRecord == null && iterator.hasNext()) { + nextRecord = getGraphElementRecord(iterator.next()); + } + + return nextRecord != null; + } + + @Override + public T next() { + if (hasNext()) + if (autoConvertToRecord) + // ACT ON WRAPPER + try { + return nextElement; + } finally { + nextElement = null; + } + else + // ACT ON RECORDS (FASTER & LIGHTER) + try { + return (T) nextRecord; + } finally { + nextRecord = null; + } + + throw new NoSuchElementException(); + } + + @Override + public void remove() { + iterator.remove(); + } + + @Override + public void setAutoConvertToRecord(final boolean convertToRecord) { + autoConvertToRecord = convertToRecord; + if (iterator instanceof OAutoConvertToRecord) + ((OAutoConvertToRecord) iterator).setAutoConvertToRecord(autoConvertToRecord); + } + + @Override + public boolean isAutoConvertToRecord() { + return autoConvertToRecord; + } + + public Object getMultiValue() { + return multiValue; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClass.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClass.java new file mode 100755 index 00000000000..b5a59b8c482 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClass.java @@ -0,0 +1,155 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Arrays; + +/** + * Iterator class to browse forward and backward the records of a cluster. Once browsed in a direction, the iterator cannot change + * it. This iterator with "live updates" set is able to catch updates to the cluster sizes while browsing. This is the case when + * concurrent clients/threads insert and remove item in any cluster the iterator is browsing. If the cluster are hot removed by from + * the database the iterator could be invalid and throw exception of cluster not found. + * + * @author Luca Garulli + */ +public class ORecordIteratorClass extends ORecordIteratorClusters { + protected final OClass targetClass; + protected boolean polymorphic; + + /** + * This method is only to maintain the retro compatibility with TinkerPop BP 2.2 + */ + public ORecordIteratorClass(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentTx iLowLevelDatabase, + final String iClassName, final boolean iPolymorphic) { + this(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, true); + } + + public ORecordIteratorClass(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final String iClassName, final boolean iPolymorphic) { + this(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, true); + } + + public ORecordIteratorClass(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final String iClassName, final boolean iPolymorphic, final boolean iterateThroughTombstones) { + this(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, iterateThroughTombstones, true); + } + + public ORecordIteratorClass(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final String iClassName, final boolean iPolymorphic, final boolean iterateThroughTombstones, boolean begin) { + this(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, iterateThroughTombstones, OStorage.LOCKING_STRATEGY.DEFAULT); + if (begin) + begin(); + } + + @Deprecated + public ORecordIteratorClass(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final String iClassName, final boolean iPolymorphic, final boolean iterateThroughTombstones, + final OStorage.LOCKING_STRATEGY iLockingStrategy) { + super(iDatabase, iLowLevelDatabase, iterateThroughTombstones, iLockingStrategy); + + targetClass = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(iClassName); + if (targetClass == null) + throw new IllegalArgumentException("Class '" + iClassName + "' was not found in database schema"); + + polymorphic = iPolymorphic; + clusterIds = polymorphic ? targetClass.getPolymorphicClusterIds() : targetClass.getClusterIds(); + clusterIds = OClassImpl.readableClusters(iDatabase, clusterIds); + + checkForSystemClusters(iDatabase, clusterIds); + + sortClusters(clusterIds); + config(); + } + + protected void sortClusters(int[] clusterIds) { + Arrays.sort(clusterIds); + } + + @SuppressWarnings("unchecked") + @Override + public REC next() { + final OIdentifiable rec = super.next(); + if (rec == null) + return null; + return (REC) rec.getRecord(); + } + + @SuppressWarnings("unchecked") + @Override + public REC previous() { + final OIdentifiable rec = super.previous(); + if (rec == null) + return null; + + return (REC) rec.getRecord(); + } + + public boolean isPolymorphic() { + return polymorphic; + } + + @Override + public String toString() { + return String.format("ORecordIteratorClass.targetClass(%s).polymorphic(%s)", targetClass, polymorphic); + } + + @Override + protected boolean include(final ORecord record) { + return record instanceof ODocument + && targetClass.isSuperClassOf(ODocumentInternal.getImmutableSchemaClass(((ODocument) record))); + } + + public OClass getTargetClass() { + return targetClass; + } + + @Override + protected void config() { + currentClusterIdx = 0; // START FROM THE FIRST CLUSTER + + updateClusterRange(); + + totalAvailableRecords = database.countClusterElements(clusterIds, isIterateThroughTombstones()); + + txEntries = database.getTransaction().getNewRecordEntriesByClass(targetClass, polymorphic); + + if (txEntries != null) + // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES + for (ORecordOperation entry : txEntries) { + if (!entry.getRecord().getIdentity().isPersistent() && entry.type != ORecordOperation.DELETED) + totalAvailableRecords++; + else if (entry.type == ORecordOperation.DELETED) + totalAvailableRecords--; + } + + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClassDescendentOrder.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClassDescendentOrder.java new file mode 100755 index 00000000000..37a84338db9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClassDescendentOrder.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.storage.OStorage; + +/** + * Record iterator to browse records in inverse order: from last to the first. + * + * @author Luca Garulli + */ +public class ORecordIteratorClassDescendentOrder extends ORecordIteratorClass { + public ORecordIteratorClassDescendentOrder(ODatabaseDocumentInternal iDatabase, ODatabaseDocumentInternal iLowLevelDatabase, + String iClassName, boolean iPolymorphic) { + this(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, false, OStorage.LOCKING_STRATEGY.NONE); + } + + @Deprecated + public ORecordIteratorClassDescendentOrder(ODatabaseDocumentInternal iDatabase, ODatabaseDocumentInternal iLowLevelDatabase, + String iClassName, boolean iPolymorphic, boolean iterateThroughTombstones, OStorage.LOCKING_STRATEGY iLockingStrategy) { + super(iDatabase, iLowLevelDatabase, iClassName, iPolymorphic, iterateThroughTombstones, iLockingStrategy); + + currentClusterIdx = clusterIds.length - 1; // START FROM THE LAST CLUSTER + updateClusterRange(); + } + + @Override protected void sortClusters(int[] clusterIds) { + super.sortClusters(clusterIds); + } + + @Override + public ORecordIteratorClusters begin() { + return super.last(); + } + + @Override + public REC next() { + return super.previous(); + } + + @Override + public boolean hasNext() { + return super.hasPrevious(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorCluster.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorCluster.java new file mode 100755 index 00000000000..b64a4a9436a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorCluster.java @@ -0,0 +1,292 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.storage.OStorage; + +/** + * Iterator class to browse forward and backward the records of a cluster. Once browsed in a direction, the iterator cannot change + * it. + * + * @author Luca Garulli + */ +public class ORecordIteratorCluster extends OIdentifiableIterator { + private ORecord currentRecord; + + public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final int iClusterId) { + this(iDatabase, iLowLevelDatabase, iClusterId, ORID.CLUSTER_POS_INVALID, ORID.CLUSTER_POS_INVALID, false, + OStorage.LOCKING_STRATEGY.DEFAULT); + } + + public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final int iClusterId, final long firstClusterEntry, final long lastClusterEntry) { + this(iDatabase, iLowLevelDatabase, iClusterId, firstClusterEntry, lastClusterEntry, false, OStorage.LOCKING_STRATEGY.NONE); + } + + @Deprecated + public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final int iClusterId, final long firstClusterEntry, final long lastClusterEntry, final boolean iterateThroughTombstones, + final OStorage.LOCKING_STRATEGY iLockingStrategy) { + super(iDatabase, iLowLevelDatabase, iterateThroughTombstones, iLockingStrategy); + + if (iClusterId == ORID.CLUSTER_ID_INVALID) + throw new IllegalArgumentException("The clusterId is invalid"); + + checkForSystemClusters(iDatabase, new int[] { iClusterId }); + + current.setClusterId(iClusterId); + final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); + + if (firstClusterEntry == ORID.CLUSTER_POS_INVALID) + this.firstClusterEntry = range[0]; + else + this.firstClusterEntry = firstClusterEntry > range[0] ? firstClusterEntry : range[0]; + + if (lastClusterEntry == ORID.CLUSTER_POS_INVALID) + this.lastClusterEntry = range[1]; + else + this.lastClusterEntry = lastClusterEntry < range[1] ? lastClusterEntry : range[1]; + + totalAvailableRecords = database.countClusterElements(current.getClusterId(), iterateThroughTombstones); + + txEntries = iDatabase.getTransaction().getNewRecordEntriesByClusterIds(new int[] { iClusterId }); + + if (txEntries != null) + // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES + for (ORecordOperation entry : txEntries) { + switch (entry.type) { + case ORecordOperation.CREATED: + totalAvailableRecords++; + break; + + case ORecordOperation.DELETED: + totalAvailableRecords--; + break; + } + } + + begin(); + } + + @Override + public boolean hasPrevious() { + checkDirection(false); + + updateRangesOnLiveUpdate(); + + if (currentRecord != null) { + return true; + } + + if (limit > -1 && browsedRecords >= limit) + // LIMIT REACHED + return false; + + boolean thereAreRecordsToBrowse = getCurrentEntry() > firstClusterEntry; + + if (thereAreRecordsToBrowse) { + ORecord record = getRecord(); + currentRecord = readCurrentRecord(record, -1); + } + + return currentRecord != null; + } + + public boolean hasNext() { + checkDirection(true); + + if (Thread.interrupted()) + // INTERRUPTED + return false; + + updateRangesOnLiveUpdate(); + + if (currentRecord != null) { + return true; + } + + if (limit > -1 && browsedRecords >= limit) + // LIMIT REACHED + return false; + + if (browsedRecords >= totalAvailableRecords) + return false; + + if (!(current.getClusterPosition() < ORID.CLUSTER_POS_INVALID) && getCurrentEntry() < lastClusterEntry) { + ORecord record = getRecord(); + try { + currentRecord = readCurrentRecord(record, +1); + } catch (Exception e) { + OLogManager.instance().error(this, "Error during read of record", e); + + final ORID recordRid = currentRecord.getIdentity(); + + if (recordRid != null) + brokenRIDs.add(recordRid.copy()); + + currentRecord = null; + } + + if (currentRecord != null) + return true; + } + + // CHECK IN TX IF ANY + if (txEntries != null) + return txEntries.size() - (currentTxEntryPosition + 1) > 0; + + return false; + } + + /** + * Return the element at the current position and move backward the cursor to the previous position available. + * + * @return the previous record found, otherwise the NoSuchElementException exception is thrown when no more records are found. + */ + @SuppressWarnings("unchecked") + @Override + public REC previous() { + checkDirection(false); + + if (currentRecord != null) { + try { + return (REC) currentRecord; + } finally { + currentRecord = null; + } + } + // ITERATE UNTIL THE PREVIOUS GOOD RECORD + while (hasPrevious()) { + try { + return (REC) currentRecord; + } finally { + currentRecord = null; + } + } + + return null; + } + + /** + * Return the element at the current position and move forward the cursor to the next position available. + * + * @return the next record found, otherwise the NoSuchElementException exception is thrown when no more records are found. + */ + @SuppressWarnings("unchecked") + public REC next() { + checkDirection(true); + + ORecord record; + + // ITERATE UNTIL THE NEXT GOOD RECORD + while (hasNext()) { + // FOUND + if (currentRecord != null) { + try { + return (REC) currentRecord; + } finally { + currentRecord = null; + } + } + + record = getTransactionEntry(); + if (record != null) + return (REC) record; + } + + return null; + } + + /** + * Move the iterator to the begin of the range. If no range was specified move to the first record of the cluster. + * + * @return The object itself + */ + @Override + public ORecordIteratorCluster begin() { + browsedRecords = 0; + + updateRangesOnLiveUpdate(); + resetCurrentPosition(); + + currentRecord = readCurrentRecord(getRecord(), +1); + + return this; + } + + /** + * Move the iterator to the end of the range. If no range was specified move to the last record of the cluster. + * + * @return The object itself + */ + @Override + public ORecordIteratorCluster last() { + browsedRecords = 0; + + updateRangesOnLiveUpdate(); + resetCurrentPosition(); + + currentRecord = readCurrentRecord(getRecord(), -1); + + return this; + } + + /** + * Tell to the iterator that the upper limit must be checked at every cycle. Useful when concurrent deletes or additions change + * the size of the cluster while you're browsing it. Default is false. + * + * @param iLiveUpdated True to activate it, otherwise false (default) + * + * @see #isLiveUpdated() + */ + @Override + public ORecordIteratorCluster setLiveUpdated(boolean iLiveUpdated) { + super.setLiveUpdated(iLiveUpdated); + + // SET THE RANGE LIMITS + if (iLiveUpdated) { + firstClusterEntry = 0L; + lastClusterEntry = Long.MAX_VALUE; + } else { + final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); + firstClusterEntry = range[0]; + lastClusterEntry = range[1]; + } + + totalAvailableRecords = database.countClusterElements(current.getClusterId(), isIterateThroughTombstones()); + + return this; + } + + private void updateRangesOnLiveUpdate() { + if (liveUpdated) { + final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); + + firstClusterEntry = range[0]; + lastClusterEntry = range[1]; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClusters.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClusters.java new file mode 100755 index 00000000000..bef3a34014c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/ORecordIteratorClusters.java @@ -0,0 +1,456 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.iterator; + +import com.orientechnologies.common.exception.OHighLevelException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * Iterator to browse multiple clusters forward and backward. Once browsed in a direction, the iterator cannot change it. This + * iterator with "live updates" set is able to catch updates to the cluster sizes while browsing. This is the case when concurrent + * clients/threads insert and remove item in any cluster the iterator is browsing. If the cluster are hot removed by from the + * database the iterator could be invalid and throw exception of cluster not found. + * + * @author Luca Garulli + */ +public class ORecordIteratorClusters extends OIdentifiableIterator { + protected int[] clusterIds; + protected int currentClusterIdx; + protected ORecord currentRecord; + protected ORID beginRange; + protected ORID endRange; + + public ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final int[] iClusterIds) { + this(iDatabase, iLowLevelDatabase, iClusterIds, false, OStorage.LOCKING_STRATEGY.NONE); + } + + @Deprecated + public ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final int[] iClusterIds, final boolean iterateThroughTombstones, final OStorage.LOCKING_STRATEGY iLockingStrategy) { + super(iDatabase, iLowLevelDatabase, iterateThroughTombstones, iLockingStrategy); + + checkForSystemClusters(iDatabase, iClusterIds); + + clusterIds = iClusterIds; + + Arrays.sort(clusterIds); + + config(); + } + + @Deprecated + protected ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, + final boolean iterateThroughTombstones, final OStorage.LOCKING_STRATEGY iLockingStrategy) { + super(iDatabase, iLowLevelDatabase, iterateThroughTombstones, iLockingStrategy); + } + + public ORecordIteratorClusters setRange(final ORID iBegin, final ORID iEnd) { + final ORID oldBegin = beginRange; + final ORID oldEnd = endRange; + + beginRange = iBegin; + endRange = iEnd; + + if ((oldBegin == null ? iBegin == null : oldBegin.equals(iBegin)) && (oldEnd == null ? iEnd == null : oldEnd.equals(iEnd))) + return this; + + if (currentRecord != null && outsideOfTheRange(currentRecord.getIdentity())) { + currentRecord = null; + } + + begin(); + return this; + } + + @Override + public boolean hasPrevious() { + checkDirection(false); + + if (currentRecord != null) + return true; + + if (limit > -1 && browsedRecords >= limit) + // LIMIT REACHED + return false; + + if (browsedRecords >= totalAvailableRecords) + return false; + + if (liveUpdated) + updateClusterRange(); + + ORecord record = getRecord(); + + // ITERATE UNTIL THE PREVIOUS GOOD RECORD + while (currentClusterIdx > -1) { + while (prevPosition()) { + currentRecord = readCurrentRecord(record, 0); + + if (currentRecord != null) + if (include(currentRecord)) + // FOUND + return true; + } + + // CLUSTER EXHAUSTED, TRY WITH THE PREVIOUS ONE + currentClusterIdx--; + + if (currentClusterIdx < 0) + break; + + updateClusterRange(); + } + + if (txEntries != null && txEntries.size() - (currentTxEntryPosition + 1) > 0) + return true; + + currentRecord = null; + return false; + } + + public boolean hasNext() { + checkDirection(true); + + if (Thread.interrupted()) + // INTERRUPTED + return false; + + if (currentRecord != null) + return true; + + if (limit > -1 && browsedRecords >= limit) + // LIMIT REACHED + return false; + + if (browsedRecords >= totalAvailableRecords) + return false; + + // COMPUTE THE NUMBER OF RECORDS TO BROWSE + if (liveUpdated) + updateClusterRange(); + + ORecord record = getRecord(); + + // ITERATE UNTIL THE NEXT GOOD RECORD + while (currentClusterIdx < clusterIds.length) { + while (nextPosition()) { + if (outsideOfTheRange(current)) + continue; + + try { + currentRecord = readCurrentRecord(record, 0); + } catch (Exception e) { + if ((e instanceof RuntimeException) && (e instanceof OHighLevelException)) + throw (RuntimeException) e; + + OLogManager.instance().error(this, "Error during read of record", e); + + currentRecord = null; + } + + if (currentRecord != null) + if (include(currentRecord)) + // FOUND + return true; + } + + // CLUSTER EXHAUSTED, TRY WITH THE NEXT ONE + currentClusterIdx++; + if (currentClusterIdx >= clusterIds.length) + break; + + updateClusterRange(); + } + + // CHECK IN TX IF ANY + if (txEntries != null && txEntries.size() - (currentTxEntryPosition + 1) > 0) + return true; + + currentRecord = null; + return false; + } + + /** + * Return the element at the current position and move forward the cursor to the next position available. + * + * @return the next record found, otherwise the NoSuchElementException exception is thrown when no more records are found. + */ + @SuppressWarnings("unchecked") + public REC next() { + checkDirection(true); + + if (currentRecord != null) + try { + // RETURN LAST LOADED RECORD + return (REC) currentRecord; + } finally { + currentRecord = null; + } + + ORecord record; + + // MOVE FORWARD IN THE CURRENT CLUSTER + while (hasNext()) { + if (currentRecord != null) + try { + // RETURN LAST LOADED RECORD + return (REC) currentRecord; + } finally { + currentRecord = null; + } + + record = getTransactionEntry(); + if (record == null) + record = readCurrentRecord(null, +1); + + if (record != null) + // FOUND + if (include(record)) + return (REC) record; + } + + record = getTransactionEntry(); + if (record != null) + return (REC) record; + + throw new NoSuchElementException( + "Direction: forward, last position was: " + current + ", range: " + beginRange + "-" + endRange); + } + + /** + * Return the element at the current position and move backward the cursor to the previous position available. + * + * @return the previous record found, otherwise the NoSuchElementException exception is thrown when no more records are found. + */ + @SuppressWarnings("unchecked") + @Override + public REC previous() { + checkDirection(false); + + if (currentRecord != null) + try { + // RETURN LAST LOADED RECORD + return (REC) currentRecord; + } finally { + currentRecord = null; + } + + ORecord record = getRecord(); + + // MOVE BACKWARD IN THE CURRENT CLUSTER + while (hasPrevious()) { + if (currentRecord != null) + try { + // RETURN LAST LOADED RECORD + return (REC) currentRecord; + } finally { + currentRecord = null; + } + + if (record == null) + record = readCurrentRecord(null, -1); + + if (record != null) + // FOUND + if (include(record)) + return (REC) record; + } + + record = getTransactionEntry(); + if (record != null) + return (REC) record; + + return null; + } + + /** + * Move the iterator to the begin of the range. If no range was specified move to the first record of the cluster. + * + * @return The object itself + */ + @Override + public ORecordIteratorClusters begin() { + if (clusterIds.length == 0) + return this; + + browsedRecords = 0; + currentClusterIdx = 0; + current.setClusterId(clusterIds[currentClusterIdx]); + + updateClusterRange(); + + resetCurrentPosition(); + nextPosition(); + + final ORecord record = getRecord(); + currentRecord = readCurrentRecord(record, 0); + + if (currentRecord != null && !include(currentRecord)) { + currentRecord = null; + hasNext(); + } + + return this; + } + + /** + * Move the iterator to the end of the range. If no range was specified move to the last record of the cluster. + * + * @return The object itself + */ + @Override + public ORecordIteratorClusters last() { + if (clusterIds.length == 0) + return this; + + browsedRecords = 0; + currentClusterIdx = clusterIds.length - 1; + + updateClusterRange(); + + current.setClusterId(clusterIds[currentClusterIdx]); + + resetCurrentPosition(); + prevPosition(); + + final ORecord record = getRecord(); + currentRecord = readCurrentRecord(record, 0); + + if (currentRecord != null && !include(currentRecord)) { + currentRecord = null; + hasPrevious(); + } + + return this; + } + + /** + * Tell to the iterator that the upper limit must be checked at every cycle. Useful when concurrent deletes or additions change + * the size of the cluster while you're browsing it. Default is false. + * + * @param iLiveUpdated True to activate it, otherwise false (default) + * @see #isLiveUpdated() + */ + @Override + public ORecordIteratorClusters setLiveUpdated(boolean iLiveUpdated) { + super.setLiveUpdated(iLiveUpdated); + + if (iLiveUpdated) { + firstClusterEntry = 0; + lastClusterEntry = Long.MAX_VALUE; + } else { + updateClusterRange(); + } + + return this; + } + + public ORID getBeginRange() { + return beginRange; + } + + public ORID getEndRange() { + return endRange; + } + + public int[] getClusterIds() { + return clusterIds; + } + + @Override + public String toString() { + return String + .format("ORecordIteratorCluster.clusters(%s).currentRecord(%s).range(%s-%s)", Arrays.toString(clusterIds), currentRecord, + beginRange, endRange); + } + + protected boolean include(final ORecord iRecord) { + return true; + } + + protected void updateClusterRange() { + if (clusterIds.length == 0) + return; + + // ADJUST IDX CHECKING BOUNDARIES + if (currentClusterIdx >= clusterIds.length) + currentClusterIdx = clusterIds.length - 1; + else if (currentClusterIdx < 0) + currentClusterIdx = 0; + + current.setClusterId(clusterIds[currentClusterIdx]); + final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); + + if (beginRange != null && beginRange.getClusterId() == current.getClusterId() && beginRange.getClusterPosition() > range[0]) + firstClusterEntry = beginRange.getClusterPosition(); + else + firstClusterEntry = range[0]; + + if (endRange != null && endRange.getClusterId() == current.getClusterId() && endRange.getClusterPosition() < range[1]) + lastClusterEntry = endRange.getClusterPosition(); + else + lastClusterEntry = range[1]; + + resetCurrentPosition(); + } + + protected void config() { + if (clusterIds.length == 0) + return; + + currentClusterIdx = 0; // START FROM THE FIRST CLUSTER + + updateClusterRange(); + + totalAvailableRecords = database.countClusterElements(clusterIds, isIterateThroughTombstones()); + + txEntries = database.getTransaction().getNewRecordEntriesByClusterIds(clusterIds); + + if (txEntries != null) + // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES + for (ORecordOperation entry : txEntries) { + if (!entry.getRecord().getIdentity().isPersistent() && entry.type != ORecordOperation.DELETED) + totalAvailableRecords++; + else if (entry.type == ORecordOperation.DELETED) + totalAvailableRecords--; + } + + begin(); + } + + private boolean outsideOfTheRange(ORID orid) { + if (beginRange != null && orid.compareTo(beginRange) < 0) + return true; + + if (endRange != null && orid.compareTo(endRange) > 0) + return true; + + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClassInterface.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClassInterface.java new file mode 100644 index 00000000000..19b711c911d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClassInterface.java @@ -0,0 +1,27 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.iterator.object; + +import java.util.Iterator; + +/** + * @author luca.molino + * + */ +public interface OObjectIteratorClassInterface extends Iterator, Iterable { + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClusterInterface.java b/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClusterInterface.java new file mode 100644 index 00000000000..f3524ac7f83 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/iterator/object/OObjectIteratorClusterInterface.java @@ -0,0 +1,27 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.iterator.object; + +import java.util.Iterator; + +/** + * @author luca.molino + * + */ +public interface OObjectIteratorClusterInterface extends Iterator, Iterable { + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadata.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadata.java new file mode 100644 index 00000000000..47370c51578 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadata.java @@ -0,0 +1,74 @@ +/* + * + * Copyright 2013 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata; + +import com.orientechnologies.orient.core.cache.OCommandCache; +import com.orientechnologies.orient.core.index.OIndexManagerProxy; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.function.OFunctionLibrary; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.metadata.security.OIdentity; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OSecurity; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceLibrary; +import com.orientechnologies.orient.core.schedule.OScheduler; + +import java.io.IOException; +import java.util.*; + +/** + * @author luca.molino + * + */ +public interface OMetadata { + Set SYSTEM_CLUSTER = Collections.unmodifiableSet(new HashSet(Arrays.asList( + new String[] { OUser.CLASS_NAME.toLowerCase(Locale.ENGLISH), ORole.CLASS_NAME.toLowerCase(Locale.ENGLISH), OIdentity.CLASS_NAME.toLowerCase(Locale.ENGLISH), + OSecurity.RESTRICTED_CLASSNAME.toLowerCase(Locale.ENGLISH), OFunction.CLASS_NAME.toLowerCase(Locale.ENGLISH), "OTriggered".toLowerCase(Locale.ENGLISH), + "OSchedule".toLowerCase(Locale.ENGLISH), "internal"}))); + + void load(); + + void create() throws IOException; + + OSchema getSchema(); + + OCommandCache getCommandCache(); + + OSecurity getSecurity(); + + OIndexManagerProxy getIndexManager(); + + int getSchemaClusterId(); + + /** + * Reloads the internal objects. + */ + void reload(); + + /** + * Closes internal objects + */ + void close(); + + OFunctionLibrary getFunctionLibrary(); + + OSequenceLibrary getSequenceLibrary(); + + OScheduler getScheduler(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataDefault.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataDefault.java new file mode 100755 index 00000000000..55d3214b7b9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataDefault.java @@ -0,0 +1,319 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.cache.OCommandCache; +import com.orientechnologies.orient.core.cache.OCommandCacheSoftRefs; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.index.OIndexManager; +import com.orientechnologies.orient.core.index.OIndexManagerProxy; +import com.orientechnologies.orient.core.index.OIndexManagerRemote; +import com.orientechnologies.orient.core.index.OIndexManagerShared; +import com.orientechnologies.orient.core.metadata.function.OFunctionLibrary; +import com.orientechnologies.orient.core.metadata.function.OFunctionLibraryImpl; +import com.orientechnologies.orient.core.metadata.function.OFunctionLibraryProxy; +import com.orientechnologies.orient.core.metadata.schema.OImmutableSchema; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.metadata.schema.OSchemaProxy; +import com.orientechnologies.orient.core.metadata.schema.OSchemaShared; +import com.orientechnologies.orient.core.metadata.security.OSecurity; +import com.orientechnologies.orient.core.metadata.security.OSecurityProxy; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceLibrary; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceLibraryImpl; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceLibraryProxy; +import com.orientechnologies.orient.core.schedule.OScheduler; +import com.orientechnologies.orient.core.schedule.OSchedulerImpl; +import com.orientechnologies.orient.core.schedule.OSchedulerProxy; +import com.orientechnologies.orient.core.security.OSecurityManager; +import com.orientechnologies.orient.core.storage.OStorageProxy; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +public class OMetadataDefault implements OMetadataInternal { + public static final String CLUSTER_INTERNAL_NAME = "internal"; + public static final String CLUSTER_INDEX_NAME = "index"; + public static final String CLUSTER_MANUAL_INDEX_NAME = "manindex"; + + protected int schemaClusterId; + + protected OSchemaProxy schema; + protected OSecurity security; + protected OIndexManagerProxy indexManager; + protected OFunctionLibraryProxy functionLibrary; + protected OSchedulerProxy scheduler; + protected OSequenceLibraryProxy sequenceLibrary; + + protected OCommandCache commandCache; + protected static final OProfiler PROFILER = Orient.instance().getProfiler(); + + private OImmutableSchema immutableSchema = null; + private int immutableCount = 0; + private ODatabaseDocumentInternal database; + + public OMetadataDefault() { + } + + public OMetadataDefault(ODatabaseDocumentInternal databaseDocument) { + this.database = databaseDocument; + } + + public void load() { + final long timer = PROFILER.startChrono(); + + try { + init(true); + } finally { + PROFILER.stopChrono(PROFILER.getDatabaseMetric(getDatabase().getName(), "metadata.load"), "Loading of database metadata", + timer, "db.*.metadata.load"); + } + } + + public void create() throws IOException { + init(false); + + schema.create(); +// schema.addBlobCluster("blob"); + indexManager.create(); + security.create(); + functionLibrary.create(); + sequenceLibrary.create(); + security.createClassTrigger(); + scheduler.create(); + + // CREATE BASE VERTEX AND EDGE CLASSES + schema.createClass("V"); + schema.createClass("E"); + } + + public OSchemaProxy getSchema() { + return schema; + } + + @Override + public OCommandCache getCommandCache() { + return commandCache; + } + + @Override + public void makeThreadLocalSchemaSnapshot() { + if (this.immutableCount == 0) { + if (schema != null) + this.immutableSchema = schema.makeSnapshot(); + } + this.immutableCount++; + } + + @Override + public void clearThreadLocalSchemaSnapshot() { + this.immutableCount--; + if (this.immutableCount == 0) { + this.immutableSchema = null; + } + } + + @Override + public OImmutableSchema getImmutableSchemaSnapshot() { + if (immutableSchema == null) { + if (schema == null) + return null; + return schema.makeSnapshot(); + } + return immutableSchema; + } + + public OSecurity getSecurity() { + return security; + } + + public OIndexManagerProxy getIndexManager() { + return indexManager; + } + + public int getSchemaClusterId() { + return schemaClusterId; + } + + private void init(final boolean iLoad) { + final ODatabaseDocumentInternal database = getDatabase(); + schemaClusterId = database.getClusterIdByName(CLUSTER_INTERNAL_NAME); + + final AtomicBoolean schemaLoaded = new AtomicBoolean(false); + + schema = new OSchemaProxy(database.getStorage().getResource(OSchema.class.getSimpleName(), new Callable() { + public OSchemaShared call() { + ODatabaseDocumentInternal database = getDatabase(); + final OSchemaShared instance = new OSchemaShared(database.getStorageVersions().classesAreDetectedByClusterId()); + if (iLoad) + instance.load(); + + schemaLoaded.set(true); + + return instance; + } + }), database); + + indexManager = new OIndexManagerProxy( + database.getStorage().getResource(OIndexManager.class.getSimpleName(), new Callable() { + public OIndexManager call() { + OIndexManager instance; + if (database.getStorage() instanceof OStorageProxy) + instance = new OIndexManagerRemote(database); + else + instance = new OIndexManagerShared(database); + + if (iLoad) + try { + instance.load(); + } catch (Exception e) { + OLogManager.instance().error(this, "[OMetadata] Error on loading index manager, reset index configuration", e); + instance.create(); + } + + return instance; + } + }), database); + + security = new OSecurityProxy(database.getStorage().getResource(OSecurity.class.getSimpleName(), + new Callable() { + public OSecurity call() { + final OSecurity instance = OSecurityManager.instance().newSecurity(); + if (iLoad) { + security = instance; + instance.load(); + } + return instance; + } + }), database); + + + commandCache = database.getStorage().getResource(OCommandCache.class.getSimpleName(), new Callable() { + public OCommandCache call() { + return new OCommandCacheSoftRefs(database.getName()); + } + }); + + final Class securityClass = (Class) database + .getProperty(ODatabase.OPTIONS.SECURITY.toString()); + if (securityClass != null) + // INSTALL CUSTOM WRAPPED SECURITY + try { + final OSecurity wrapped = security; + security = securityClass.getDeclaredConstructor(OSecurity.class, ODatabaseDocumentInternal.class).newInstance(wrapped, + database); + } catch (Exception e) { + throw OException + .wrapException(new OSecurityException("Cannot install custom security implementation (" + securityClass + ")"), e); + } + + functionLibrary = new OFunctionLibraryProxy( + database.getStorage().getResource(OFunctionLibrary.class.getSimpleName(), new Callable() { + public OFunctionLibrary call() { + final OFunctionLibraryImpl instance = new OFunctionLibraryImpl(); + if (iLoad && !(database.getStorage() instanceof OStorageProxy)) + instance.load(); + return instance; + } + }), database); + sequenceLibrary = new OSequenceLibraryProxy( + database.getStorage().getResource(OSequenceLibrary.class.getSimpleName(), new Callable() { + @Override + public OSequenceLibrary call() throws Exception { + final OSequenceLibraryImpl instance = new OSequenceLibraryImpl(); + if (iLoad) { + instance.load(); + } + return instance; + } + }), database); + scheduler = new OSchedulerProxy( + database.getStorage().getResource(OScheduler.class.getSimpleName(), new Callable() { + public OScheduler call() { + final OSchedulerImpl instance = new OSchedulerImpl(); + if (iLoad && !(database.getStorage() instanceof OStorageProxy)) + instance.load(); + return instance; + } + }), database); + + if (schemaLoaded.get()) + schema.onPostIndexManagement(); + } + + /** + * Reloads the internal objects. + */ + public void reload() { + if (schema != null) + schema.reload(); + if (indexManager != null) + indexManager.reload(); + if (security != null) + security.load(); + if (functionLibrary != null) + functionLibrary.load(); + if (sequenceLibrary != null) + sequenceLibrary.load(); + if (commandCache != null) + commandCache.clear(); + if (scheduler!= null) + scheduler.load(); + } + + /** + * Closes internal objects + */ + public void close() { + if (scheduler!= null) + scheduler.close(); + if (schema != null) + schema.close(); + if (security != null) + security.close(false); + if (commandCache != null) { + commandCache.clear(); + commandCache.shutdown(); + } + } + + protected ODatabaseDocumentInternal getDatabase() { + return database; + } + + public OFunctionLibrary getFunctionLibrary() { + return functionLibrary; + } + + @Override + public OSequenceLibrary getSequenceLibrary() { + return sequenceLibrary; + } + + public OScheduler getScheduler() { + return scheduler; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataInternal.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataInternal.java new file mode 100644 index 00000000000..0d5321d8496 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/OMetadataInternal.java @@ -0,0 +1,32 @@ +/* + * + * Copyright 2013 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata; + +import com.orientechnologies.orient.core.metadata.schema.OImmutableSchema; + +/** + * Internal interface to manage metadata snapshots. + */ +public interface OMetadataInternal extends OMetadata { + + void makeThreadLocalSchemaSnapshot(); + + void clearThreadLocalSchemaSnapshot(); + + OImmutableSchema getImmutableSchemaSnapshot(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunction.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunction.java new file mode 100644 index 00000000000..e3b615900b9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunction.java @@ -0,0 +1,108 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; + +import java.util.List; + +/** + * Dynamic function factory bound to the database's functions + * + * @author Luca Garulli + * + */ +public class ODatabaseFunction implements OSQLFunction { + private final OFunction f; + + public ODatabaseFunction(final OFunction f) { + this.f = f; + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iFuncParams, final OCommandContext iContext) { + return f.executeInContext(iContext, iFuncParams); + } + + @Override + public boolean aggregateResults() { + return false; + } + + @Override + public boolean filterResult() { + return false; + } + + @Override + public String getName() { + return f.getName(); + } + + @Override + public int getMinParams() { + return 0; + } + + @Override + public int getMaxParams() { + return f.getParameters() != null ? f.getParameters().size() : 0; + } + + @Override + public String getSyntax() { + final StringBuilder buffer = new StringBuilder(512); + buffer.append(f.getName()); + buffer.append('('); + final List params = f.getParameters(); + for (int p = 0; p < params.size(); ++p) { + if (p > 0) + buffer.append(','); + buffer.append(params.get(p)); + } + buffer.append(')'); + return buffer.toString(); + } + + @Override + public Object getResult() { + return null; + } + + @Override + public void setResult(final Object iResult) { + } + + @Override + public void config(final Object[] configuredParameters) { + } + + @Override + public boolean shouldMergeDistributedResult() { + return false; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunctionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunctionFactory.java new file mode 100644 index 00000000000..588e621efaa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/ODatabaseFunctionFactory.java @@ -0,0 +1,55 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import java.util.Set; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionFactory; + +/** + * Dynamic function factory bound to the database's functions + * + * @author Luca Garulli + * + */ +public class ODatabaseFunctionFactory implements OSQLFunctionFactory { + @Override + public boolean hasFunction(final String iName) { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + return db.getMetadata().getFunctionLibrary().getFunction(iName) != null; + } + + @Override + public Set getFunctionNames() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + return db.getMetadata().getFunctionLibrary().getFunctionNames(); + } + + @Override + public OSQLFunction createFunction(final String name) throws OCommandExecutionException { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OFunction f = db.getMetadata().getFunctionLibrary().getFunction(name); + return new ODatabaseFunction(f); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunction.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunction.java new file mode 100644 index 00000000000..b99265832b2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunction.java @@ -0,0 +1,227 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.script.OCommandExecutorFunction; +import com.orientechnologies.orient.core.command.script.OCommandExecutorScript; +import com.orientechnologies.orient.core.command.script.OCommandFunction; +import com.orientechnologies.orient.core.command.script.OCommandScript; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.ORetryQueryException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Stored function. It contains language and code to execute as a function. The execute() takes parameters. The function is + * state-less, so can be used by different threads. + * + * @author Luca Garulli + */ +public class OFunction extends ODocumentWrapper { + public static final String CLASS_NAME = "OFunction"; + private OCallable> callback; + + /** + * Creates a new function. + */ + public OFunction() { + super(CLASS_NAME); + setLanguage("SQL"); + } + + /** + * Creates a new function wrapping the saved document. + * + * @param iDocument + * Document to assign + */ + public OFunction(final ODocument iDocument) { + super(iDocument); + } + + /** + * Loads a function. + * + * @param iRid + * RID of the function to load + */ + public OFunction(final ORecordId iRid) { + super(iRid); + } + + public String getName() { + return document.field("name"); + } + + public OFunction setName(final String iName) { + document.field("name", iName); + return this; + } + + public String getCode() { + return document.field("code"); + } + + public OFunction setCode(final String iCode) { + document.field("code", iCode); + return this; + } + + public String getLanguage() { + return document.field("language"); + } + + public OFunction setLanguage(final String iLanguage) { + document.field("language", iLanguage); + return this; + } + + public List getParameters() { + return document.field("parameters"); + } + + public OFunction setParameters(final List iParameters) { + document.field("parameters", iParameters); + return this; + } + + public boolean isIdempotent() { + final Boolean idempotent = document.field("idempotent"); + return idempotent != null && idempotent; + } + + public OFunction setIdempotent(final boolean iIdempotent) { + document.field("idempotent", iIdempotent); + return this; + } + + public OCallable> getCallback() { + return callback; + } + + public OFunction setCallback(final OCallable> callback) { + this.callback = callback; + return this; + } + + public Object execute(final Object... iArgs) { + return executeInContext(null, iArgs); + } + + public Object executeInContext(final OCommandContext iContext, final Object... iArgs) { + final List params = getParameters(); + + // CONVERT PARAMETERS IN A MAP + Map args = null; + + if (iArgs.length > 0) { + args = new LinkedHashMap(); + for (int i = 0; i < iArgs.length; ++i) { + // final Object argValue = ORecordSerializerStringAbstract.getTypeValue(iArgs[i].toString()); + final Object argValue = iArgs[i]; + + if (params != null && i < params.size()) + args.put(params.get(i), argValue); + else + args.put("param" + i, argValue); + } + } + + if (callback != null) { + // EXECUTE CALLBACK + return callback.call(args); + } + + // EXECUTE DB FUNCTION + final OCommandExecutorFunction command = new OCommandExecutorFunction(); + command.parse(new OCommandFunction(getName())); + return command.executeInContext(iContext, args); + } + + public Object executeInContext(final OCommandContext iContext, final Map iArgs) { + // CONVERT PARAMETERS IN A MAP + final Map args = new LinkedHashMap(); + + if (iArgs.size() > 0) { + // PRESERVE THE ORDER FOR PARAMETERS (ARE USED AS POSITIONAL) + final List params = getParameters(); + for (String p : params) { + args.put(p, iArgs.get(p)); + } + } + + if (callback != null) { + // EXECUTE CALLBACK + return callback.call(args); + } + + // EXECUTE DB FUNCTION + final OCommandExecutorFunction command = new OCommandExecutorFunction(); + command.parse(new OCommandFunction(getName())); + return command.executeInContext(iContext, args); + } + + public Object execute(final Map iArgs) { + final long start = Orient.instance().getProfiler().startChrono(); + + Object result; + while (true) { + try { + if (callback != null) + return callback.call(iArgs); + + final OCommandExecutorScript command = new OCommandExecutorScript(); + command.parse(new OCommandScript(getLanguage(), getCode())); + result = command.execute(iArgs); + break; + + } catch (ONeedRetryException e) { + continue; + } catch (ORetryQueryException e) { + continue; + } + } + + if (Orient.instance().getProfiler().isRecording()) + Orient.instance().getProfiler().stopChrono("db." + ODatabaseRecordThreadLocal.INSTANCE.get().getName() + ".function.execute", + "Time to execute a function", start, "db.*.function.execute"); + + return result; + } + + public ORID getId() { + return document.getIdentity(); + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionDuplicatedException.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionDuplicatedException.java new file mode 100644 index 00000000000..ceffa23c395 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionDuplicatedException.java @@ -0,0 +1,12 @@ +package com.orientechnologies.orient.core.metadata.function; + +import com.orientechnologies.common.exception.OException; + +/** + * Created by tglman on 11/02/16. + */ +public class OFunctionDuplicatedException extends OException { + public OFunctionDuplicatedException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibrary.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibrary.java new file mode 100644 index 00000000000..499128de05b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibrary.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import java.util.Set; + +/** + * Manages stored functions. + * + * @author Luca Garulli + */ +public interface OFunctionLibrary { + Set getFunctionNames(); + + OFunction getFunction(String iName); + + OFunction createFunction(String iName); + + void dropFunction(String iName); + + void dropFunction(OFunction function); + + void create(); + + void load(); + + void close(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryImpl.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryImpl.java new file mode 100755 index 00000000000..adfeb4ee147 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryImpl.java @@ -0,0 +1,152 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.command.OCommandManager; +import com.orientechnologies.orient.core.command.script.OCommandExecutorFunction; +import com.orientechnologies.orient.core.command.script.OCommandFunction; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages stored functions. + * + * @author Luca Garulli + */ +public class OFunctionLibraryImpl implements OFunctionLibrary { + protected Map functions = new ConcurrentHashMap(); + + static { + OCommandManager.instance().registerExecutor(OCommandFunction.class, OCommandExecutorFunction.class); + } + + public OFunctionLibraryImpl() { + } + + public void create() { + init(); + } + + public void load() { + // COPY CALLBACK IN RAM + final Map>> callbacks = new HashMap>>(); + for (Map.Entry entry : functions.entrySet()) { + if (entry.getValue().getCallback() != null) + callbacks.put(entry.getKey(), entry.getValue().getCallback()); + } + + functions.clear(); + + // LOAD ALL THE FUNCTIONS IN MEMORY + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot().existsClass("OFunction")) { + List result = db.query(new OSQLSynchQuery("select from OFunction order by name")); + for (ODocument d : result) { + d.reload(); + + //skip the function records which do not contain real data + if (d.fields() == 0) + continue; + + final OFunction f = new OFunction(d); + + // RESTORE CALLBACK IF ANY + f.setCallback(callbacks.get(f.getName())); + + functions.put(d.field("name").toString().toUpperCase(Locale.ENGLISH), f); + } + } + } + + public Set getFunctionNames() { + return Collections.unmodifiableSet(functions.keySet()); + } + + public OFunction getFunction(final String iName) { + return functions.get(iName.toUpperCase(Locale.ENGLISH)); + } + + public synchronized OFunction createFunction(final String iName) { + init(); + + final OFunction f = new OFunction().setName(iName); + try { + f.save(); + } catch (ORecordDuplicatedException ex) { + throw OException.wrapException(new OFunctionDuplicatedException("Function with name '" + iName + "' already exist"), null); + } + functions.put(iName.toUpperCase(Locale.ENGLISH), f); + + return f; + } + + public void close() { + functions.clear(); + } + + protected void init() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (db.getMetadata().getSchema().existsClass("OFunction")) { + final OClass f = db.getMetadata().getSchema().getClass("OFunction"); + OProperty prop = f.getProperty("name"); + if (prop.getAllIndexes().isEmpty()) + prop.createIndex(OClass.INDEX_TYPE.UNIQUE_HASH_INDEX); + return; + } + + final OClass f = db.getMetadata().getSchema().createClass("OFunction"); + OProperty prop = f.createProperty("name", OType.STRING, (OType) null, true); + prop.set(OProperty.ATTRIBUTES.NOTNULL, true); + prop.set(OProperty.ATTRIBUTES.MANDATORY, true); + prop.createIndex(OClass.INDEX_TYPE.UNIQUE_HASH_INDEX); + f.createProperty("code", OType.STRING, (OType) null, true); + f.createProperty("language", OType.STRING, (OType) null, true); + f.createProperty("idempotent", OType.BOOLEAN, (OType) null, true); + f.createProperty("parameters", OType.EMBEDDEDLIST, OType.STRING, true); + } + + @Override + public synchronized void dropFunction(OFunction function) { + String name = function.getName(); + ODocument doc = function.getDocument(); + doc.delete(); + functions.remove(name.toUpperCase(Locale.ENGLISH)); + } + + @Override + public synchronized void dropFunction(String iName) { + OFunction function = getFunction(iName); + ODocument doc = function.getDocument(); + doc.delete(); + functions.remove(iName.toUpperCase(Locale.ENGLISH)); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryProxy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryProxy.java new file mode 100644 index 00000000000..5bd75906be7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionLibraryProxy.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import java.util.Set; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OProxedResource; + +/** + * Proxy class to access to the centralized Function Library instance. + * + * @author Luca Garulli + */ +public class OFunctionLibraryProxy extends OProxedResource implements OFunctionLibrary { + public OFunctionLibraryProxy(final OFunctionLibrary iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + @Override + public Set getFunctionNames() { + return delegate.getFunctionNames(); + } + + @Override + public OFunction getFunction(final String iName) { + return delegate.getFunction(iName); + } + + @Override + public OFunction createFunction(final String iName) { + return delegate.createFunction(iName); + } + + @Override + public void create() { + delegate.create(); + } + + @Override + public void load() { + delegate.load(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public void dropFunction(OFunction function) { + delegate.dropFunction(function); + } + + @Override + public void dropFunction(String iName) { + delegate.dropFunction(iName); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionTrigger.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionTrigger.java new file mode 100755 index 00000000000..0459ccaddb2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionTrigger.java @@ -0,0 +1,85 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Update the in-memory function library. + * + * @author Luca Garulli + */ +public class OFunctionTrigger extends ODocumentHookAbstract implements ORecordHook.Scoped { + + public static final String CLASSNAME = "OFunction"; + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + public OFunctionTrigger(ODatabaseDocument database) { + super(database); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } + + @Override + public RESULT onTrigger(TYPE iType, ORecord iRecord) { + OImmutableClass clazz = null; + if (iRecord instanceof ODocument) + clazz = ODocumentInternal.getImmutableSchemaClass((ODocument) iRecord); + if (clazz == null || !clazz.isFunction()) + return RESULT.RECORD_NOT_CHANGED; + return super.onTrigger(iType, iRecord); + } + + @Override + public void onRecordAfterCreate(final ODocument iDocument) { + reloadLibrary(); + } + + @Override + public void onRecordAfterUpdate(final ODocument iDocument) { + reloadLibrary(); + } + + @Override + public void onRecordAfterDelete(final ODocument iDocument) { + reloadLibrary(); + } + + protected void reloadLibrary() { + database.getMetadata().getFunctionLibrary().load(); + + Orient.instance().getScriptManager().close(database.getName()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionUtilWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionUtilWrapper.java new file mode 100644 index 00000000000..715525059e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/function/OFunctionUtilWrapper.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.function; + +/** + * Wrapper of function with additional utility methods to help inside functions. + * + * @author Luca Garulli + * + */ +public class OFunctionUtilWrapper { + + public OFunctionUtilWrapper() { + } + + public boolean exists(final Object... iValues) { + if (iValues != null) + for (Object o : iValues) + if (o != null && !o.equals("undefined") && !o.equals("null")) + return true; + return false; + } + + public boolean containsArray(final Object[] iArray, final Object value) { + if (iArray != null && value != null) + for (Object o : iArray) + if (o != null && o.equals(value)) + return true; + return false; + } + + public Object value(final Object iValue) { + return iValue != null ? iValue : null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClass.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClass.java new file mode 100755 index 00000000000..d2ad0b3a173 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClass.java @@ -0,0 +1,483 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Schema class + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public interface OClass extends Comparable { + + public static final String EDGE_CLASS_NAME = "E"; + public static final String VERTEX_CLASS_NAME = "V"; + + + enum ATTRIBUTES { + NAME, SHORTNAME, SUPERCLASS, SUPERCLASSES, OVERSIZE, STRICTMODE, ADDCLUSTER, REMOVECLUSTER, CUSTOM, ABSTRACT, CLUSTERSELECTION, DESCRIPTION, ENCRYPTION + } + + enum INDEX_TYPE { + UNIQUE(true), NOTUNIQUE(true), FULLTEXT(true), DICTIONARY(false), PROXY(true), UNIQUE_HASH_INDEX(true), NOTUNIQUE_HASH_INDEX( + true), FULLTEXT_HASH_INDEX(true), DICTIONARY_HASH_INDEX(false), SPATIAL(true); + + private boolean automaticIndexable; + + INDEX_TYPE(boolean iValue) { + automaticIndexable = iValue; + } + + boolean isAutomaticIndexable() { + return automaticIndexable; + } + } + + + + boolean isAbstract(); + + OClass setAbstract(boolean iAbstract); + + boolean isStrictMode(); + + OClass setStrictMode(boolean iMode); + + @Deprecated + OClass getSuperClass(); + + @Deprecated + OClass setSuperClass(OClass iSuperClass); + + boolean hasSuperClasses(); + + List getSuperClassesNames(); + + List getSuperClasses(); + + OClass setSuperClasses(List classes); + + OClass addSuperClass(OClass superClass); + + OClass removeSuperClass(OClass superClass); + + String getName(); + + OClass setName(String iName); + + String getDescription(); + + OClass setDescription(String iDescription); + + String getStreamableName(); + + Collection declaredProperties(); + + Collection properties(); + + Map propertiesMap(); + + Collection getIndexedProperties(); + + OProperty getProperty(String iPropertyName); + + OProperty createProperty(String iPropertyName, OType iType); + + OProperty createProperty(String iPropertyName, OType iType, OClass iLinkedClass); + + /** + * Create a property in the class with the specified options. + * + * @param iPropertyName + * the name of the property. + * @param iType + * the type of the property. + * @param iLinkedClass + * in case of property of type LINK,LINKLIST,LINKSET,LINKMAP,EMBEDDED,EMBEDDEDLIST,EMBEDDEDSET,EMBEDDEDMAP can be + * specified a linked class in all the other cases should be null + * @param iUnsafe + * if true avoid to check the persistent data for compatibility, should be used only if all persistent data is compatible + * with the property + * @return the created property. + */ + OProperty createProperty(String iPropertyName, OType iType, OClass iLinkedClass, boolean iUnsafe); + + OProperty createProperty(String iPropertyName, OType iType, OType iLinkedType); + + /** + * Create a property in the class with the specified options. + * + * @param iPropertyName + * the name of the property. + * @param iType + * the type of the property. + * @param iLinkedType + * in case of property of type EMBEDDEDLIST,EMBEDDEDSET,EMBEDDEDMAP can be specified a linked type in all the other cases + * should be null + * @param iUnsafe + * if true avoid to check the persistent data for compatibility, should be used only if all persistent data is compatible + * with the property + * @return the created property. + */ + OProperty createProperty(String iPropertyName, OType iType, OType iLinkedType, boolean iUnsafe); + + void dropProperty(String iPropertyName); + + boolean existsProperty(String iPropertyName); + + int getClusterForNewInstance(ODocument doc); + + int getDefaultClusterId(); + + void setDefaultClusterId(int iDefaultClusterId); + + int[] getClusterIds(); + + OClass addClusterId(int iId); + + OClusterSelectionStrategy getClusterSelection(); + + OClass setClusterSelection(OClusterSelectionStrategy clusterSelection); + + OClass setClusterSelection(String iStrategyName); + + OClass addCluster(String iClusterName); + + /** + * Removes all data in the cluster with given name. + * As result indexes for this class will be rebuilt. + * + * @param clusterName Name of cluster to be truncated. + * @return Instance of current object. + */ + OClass truncateCluster(String clusterName); + + OClass removeClusterId(int iId); + + int[] getPolymorphicClusterIds(); + + @Deprecated + Collection getBaseClasses(); + + @Deprecated + Collection getAllBaseClasses(); + + /** + * @return all the subclasses (one level hierarchy only) + */ + Collection getSubclasses(); + + /** + * @return all the subclass hierarchy + */ + Collection getAllSubclasses(); + + /** + * @return all recursively collected super classes + */ + Collection getAllSuperClasses(); + + long getSize(); + + float getClassOverSize(); + + /** + * Returns the oversize factor. Oversize is used to extend the record size by a factor to avoid defragmentation upon updates. 0 or + * 1.0 means no oversize. + * + * @return Oversize factor + * @see #setOverSize(float) + */ + float getOverSize(); + + /** + * Sets the oversize factor. Oversize is used to extend the record size by a factor to avoid defragmentation upon updates. 0 or + * 1.0 means no oversize. Default is 0. + * + * @return Oversize factor + * @see #getOverSize() + */ + OClass setOverSize(float overSize); + + /** + * Returns the number of the records of this class considering also subclasses (polymorphic). + */ + long count(); + + /** + * Returns the number of the records of this class and based on polymorphic parameter it consider or not the subclasses. + */ + long count(boolean iPolymorphic); + + /** + * Truncates all the clusters the class uses. + * + * @throws IOException + */ + void truncate() throws IOException; + + /** + * Tells if the current instance extends the passed schema class (iClass). + * + * @param iClassName + * @return true if the current instance extends the passed schema class (iClass). + * @see #isSuperClassOf(OClass) + */ + boolean isSubClassOf(String iClassName); + + /** + * Returns true if the current instance extends the passed schema class (iClass). + * + * @param iClass + * @return true if the current instance extends the passed schema class (iClass). + * @see #isSuperClassOf(OClass) + */ + boolean isSubClassOf(OClass iClass); + + /** + * Returns true if the passed schema class (iClass) extends the current instance. + * + * @param iClass + * @return Returns true if the passed schema class extends the current instance. + * @see #isSubClassOf(OClass) + */ + boolean isSuperClassOf(OClass iClass); + + String getShortName(); + + OClass setShortName(String shortName); + + Object get(ATTRIBUTES iAttribute); + + OClass set(ATTRIBUTES attribute, Object iValue); + + /** + * Creates database index that is based on passed in field names. Given index will be added into class instance and associated + * with database index. + * + * @param fields + * Field names from which index will be created. + * @param iName + * Database index name + * @param iType + * Index type. + * @return Class index registered inside of given class ans associated with database index. + */ + OIndex createIndex(String iName, INDEX_TYPE iType, String... fields); + + /** + * Creates database index that is based on passed in field names. Given index will be added into class instance and associated + * with database index. + * + * @param fields + * Field names from which index will be created. + * @param iName + * Database index name + * @param iType + * Index type. + * @return Class index registered inside of given class ans associated with database index. + */ + OIndex createIndex(String iName, String iType, String... fields); + + /** + * Creates database index that is based on passed in field names. Given index will be added into class instance. + * + * @param fields + * Field names from which index will be created. + * @param iName + * Database index name. + * @param iType + * Index type. + * @param iProgressListener + * Progress listener. + * @return Class index registered inside of given class ans associated with database index. + */ + OIndex createIndex(String iName, INDEX_TYPE iType, OProgressListener iProgressListener, String... fields); + + /** + * Creates database index that is based on passed in field names. Given index will be added into class instance. + * + * @param iName + * Database index name. + * @param iType + * Index type. + * @param iProgressListener + * Progress listener. + * @param metadata + * Additional parameters which will be added in index configuration document as "metadata" field. + * @param algorithm + * Algorithm to use for indexing. + * @param fields + * Field names from which index will be created. @return Class index registered inside of given class ans associated with + * database index. + */ + OIndex createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata, String algorithm, + String... fields); + + /** + * Creates database index that is based on passed in field names. Given index will be added into class instance. + * + * @param iName + * Database index name. + * @param iType + * Index type. + * @param iProgressListener + * Progress listener. + * @param metadata + * Additional parameters which will be added in index configuration document as "metadata" field. + * @param fields + * Field names from which index will be created. @return Class index registered inside of given class ans associated with + * database index. + */ + OIndex createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata, String... fields); + + /** + * Returns list of indexes that contain passed in fields names as their first keys. Order of fields does not matter. + *

          + * All indexes sorted by their count of parameters in ascending order. If there are indexes for the given set of fields in super + * class they will be taken into account. + * + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + * @see com.orientechnologies.orient.core.index.OIndexDefinition#getParamCount() + */ + Set> getInvolvedIndexes(Collection fields); + + /** + * Returns list of indexes that contain passed in fields names as their first keys. Order of fields does not matter. + *

          + * All indexes sorted by their count of parameters in ascending order. If there are indexes for the given set of fields in super + * class they will be taken into account. + * + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + * @see #getInvolvedIndexes(java.util.Collection) + */ + Set> getInvolvedIndexes(String... fields); + + /** + * Returns list of indexes that contain passed in fields names as their first keys. Order of fields does not matter. + *

          + * Indexes that related only to the given class will be returned. + * + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + * @see com.orientechnologies.orient.core.index.OIndexDefinition#getParamCount() + */ + Set> getClassInvolvedIndexes(Collection fields); + + /** + * @param fields + * Field names. + * @return list of indexes that contain passed in fields names as their first keys. + * @see #getClassInvolvedIndexes(java.util.Collection) + */ + Set> getClassInvolvedIndexes(String... fields); + + /** + * Indicates whether given fields are contained as first key fields in class indexes. Order of fields does not matter. If there + * are indexes for the given set of fields in super class they will be taken into account. + * + * @param fields + * Field names. + * @return true if given fields are contained as first key fields in class indexes. + */ + boolean areIndexed(Collection fields); + + /** + * @param fields + * Field names. + * @return true if given fields are contained as first key fields in class indexes. + * @see #areIndexed(java.util.Collection) + */ + boolean areIndexed(String... fields); + + /** + * Returns index instance by database index name. + * + * @param iName + * Database index name. + * @return Index instance. + */ + OIndex getClassIndex(String iName); + + /** + * @return All indexes for given class, not the inherited ones. + */ + Set> getClassIndexes(); + + /** + * Internal. Copy all the indexes for given class, not the inherited ones, in the collection received as argument. + */ + void getClassIndexes(Collection> indexes); + + /** + * Internal. All indexes for given class and its super classes. + */ + void getIndexes(Collection> indexes); + + /** + * @return All indexes for given class and its super classes. + */ + Set> getIndexes(); + + /** + * Returns the auto sharding index configured for the class if any. + */ + OIndex getAutoShardingIndex(); + + /** + * @return true if this class represents a subclass of an edge class (E) + */ + boolean isEdgeType(); + + /** + * @return true if this class represents a subclass of a vertex class (V) + */ + boolean isVertexType(); + + + String getCustom(String iName); + + OClass setCustom(String iName, String iValue); + + void removeCustom(String iName); + + void clearCustom(); + + Set getCustomKeys(); + + boolean hasClusterId(int clusterId); + + boolean hasPolymorphicClusterId(int clusterId); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassAbstractDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassAbstractDelegate.java new file mode 100644 index 00000000000..f5b748f1041 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassAbstractDelegate.java @@ -0,0 +1,510 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info--at--orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Abstract Delegate for OClass interface. + * + * @author Luca Garulli (http://www.orientechnologies.com) + */ +public abstract class OClassAbstractDelegate implements OClass { + + protected final OClass delegate; + + public OClassAbstractDelegate(final OClass delegate) { + if (delegate == null) + throw new IllegalArgumentException("Class is null"); + + this.delegate = delegate; + } + + @Override + public OIndex getAutoShardingIndex() { + return delegate.getAutoShardingIndex(); + } + + @Override + public boolean isStrictMode() { + return delegate.isStrictMode(); + } + + @Override + public boolean isAbstract() { + return delegate.isAbstract(); + } + + @Override + public OClass setAbstract(final boolean iAbstract) { + delegate.setAbstract(iAbstract); + return this; + } + + @Override + public OClass setStrictMode(final boolean iMode) { + delegate.setStrictMode(iMode); + return this; + } + + @Override + public OClass getSuperClass() { + return delegate.getSuperClass(); + } + + @Override + public OClass setSuperClass(final OClass iSuperClass) { + delegate.setSuperClass(iSuperClass); + return this; + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public List getSuperClasses() { + return delegate.getSuperClasses(); + } + + @Override + public boolean hasSuperClasses() { + return delegate.hasSuperClasses(); + } + + @Override + public OClass setSuperClasses(final List classes) { + delegate.setSuperClasses(classes); + return this; + } + + @Override + public List getSuperClassesNames() { + return delegate.getSuperClassesNames(); + } + + @Override + public void getIndexes(final Collection> indexes) { + delegate.getIndexes(indexes); + } + + @Override + public OClass addSuperClass(final OClass superClass) { + delegate.addSuperClass(superClass); + return this; + } + + @Override + public OClass removeSuperClass(final OClass superClass) { + delegate.removeSuperClass(superClass); + return this; + } + + @Override + public OClass setName(final String iName) { + delegate.setName(iName); + return this; + } + + @Override + public String getStreamableName() { + return delegate.getStreamableName(); + } + + @Override + public Collection declaredProperties() { + return delegate.declaredProperties(); + } + + @Override + public Collection properties() { + return delegate.properties(); + } + + @Override + public Map propertiesMap() { + return delegate.propertiesMap(); + } + + @Override + public Collection getIndexedProperties() { + return delegate.getIndexedProperties(); + } + + @Override + public OProperty getProperty(String iPropertyName) { + return delegate.getProperty(iPropertyName); + } + + @Override + public OProperty createProperty(final String iPropertyName, final OType iType) { + return delegate.createProperty(iPropertyName, iType); + } + + @Override + public OProperty createProperty(final String iPropertyName, final OType iType, final OClass iLinkedClass) { + return delegate.createProperty(iPropertyName, iType, iLinkedClass); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OClass iLinkedClass, boolean unsafe) { + return delegate.createProperty(iPropertyName, iType, iLinkedClass, unsafe); + } + + @Override + public OProperty createProperty(final String iPropertyName, final OType iType, final OType iLinkedType) { + return delegate.createProperty(iPropertyName, iType, iLinkedType); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OType iLinkedType, boolean unsafe) { + return delegate.createProperty(iPropertyName, iType, iLinkedType, unsafe); + } + + @Override + public void dropProperty(final String iPropertyName) { + delegate.dropProperty(iPropertyName); + } + + @Override + public boolean existsProperty(final String iPropertyName) { + return delegate.existsProperty(iPropertyName); + } + + @Override + public int getClusterForNewInstance(final ODocument doc) { + return delegate.getClusterForNewInstance(doc); + } + + @Override + public int getDefaultClusterId() { + return delegate.getDefaultClusterId(); + } + + @Override + public void setDefaultClusterId(final int iDefaultClusterId) { + delegate.setDefaultClusterId(iDefaultClusterId); + } + + @Override + public int[] getClusterIds() { + return delegate.getClusterIds(); + } + + @Override + public OClass addClusterId(final int iId) { + delegate.addClusterId(iId); + return this; + } + + @Override + public OClusterSelectionStrategy getClusterSelection() { + return delegate.getClusterSelection(); + } + + @Override + public OClass setClusterSelection(final OClusterSelectionStrategy clusterSelection) { + delegate.setClusterSelection(clusterSelection); + return this; + } + + @Override + public OClass setClusterSelection(final String iStrategyName) { + delegate.setClusterSelection(iStrategyName); + return this; + } + + @Override + public OClass addCluster(final String iClusterName) { + delegate.addCluster(iClusterName); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public OClass truncateCluster(String clusterName) { + delegate.truncateCluster(clusterName); + + return this; + } + + @Override + public OClass removeClusterId(final int iId) { + delegate.removeClusterId(iId); + return this; + } + + @Override + public int[] getPolymorphicClusterIds() { + return delegate.getPolymorphicClusterIds(); + } + + @Override + public Collection getSubclasses() { + return delegate.getSubclasses(); + } + + @Override + public Collection getBaseClasses() { + return delegate.getSubclasses(); + } + + @Override + public Collection getAllSubclasses() { + return delegate.getAllSubclasses(); + } + + @Override + public Collection getAllSuperClasses() { + return delegate.getAllSuperClasses(); + } + + @Override + public Collection getAllBaseClasses() { + return delegate.getAllSubclasses(); + } + + @Override + public long getSize() { + return delegate.getSize(); + } + + @Override + public float getOverSize() { + return delegate.getOverSize(); + } + + @Override + public OClass setOverSize(final float overSize) { + delegate.setOverSize(overSize); + return this; + } + + @Override + public long count() { + return delegate.count(); + } + + @Override + public long count(final boolean iPolymorphic) { + return delegate.count(iPolymorphic); + } + + @Override + public void truncate() throws IOException { + delegate.truncate(); + } + + @Override + public boolean isSubClassOf(final String iClassName) { + return delegate.isSubClassOf(iClassName); + } + + @Override + public boolean isSubClassOf(final OClass iClass) { + return delegate.isSubClassOf(iClass); + } + + @Override + public boolean isSuperClassOf(final OClass iClass) { + return delegate.isSuperClassOf(iClass); + } + + @Override + public String getShortName() { + return delegate.getShortName(); + } + + @Override + public OClass setShortName(final String shortName) { + delegate.setShortName(shortName); + return this; + } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public OClass setDescription(String iDescription) { + delegate.setDescription(iDescription); + return this; + } + + @Override + public Object get(ATTRIBUTES iAttribute) { + return delegate.get(iAttribute); + } + + @Override + public OClass set(ATTRIBUTES attribute, Object iValue) { + delegate.set(attribute, iValue); + return this; + } + + @Override + public OIndex createIndex(final String iName, final INDEX_TYPE iType, final String... fields) { + return delegate.createIndex(iName, iType, fields); + } + + @Override + public OIndex createIndex(final String iName, final String iType, final String... fields) { + return delegate.createIndex(iName, iType, fields); + } + + @Override + public OIndex createIndex(final String iName, final INDEX_TYPE iType, final OProgressListener iProgressListener, + final String... fields) { + return delegate.createIndex(iName, iType, iProgressListener, fields); + } + + @Override + public OIndex createIndex(final String iName, final String iType, final OProgressListener iProgressListener, + final ODocument metadata, String algorithm, String... fields) { + return delegate.createIndex(iName, iType, iProgressListener, metadata, algorithm, fields); + } + + @Override + public OIndex createIndex(final String iName, final String iType, final OProgressListener iProgressListener, + final ODocument metadata, String... fields) { + return delegate.createIndex(iName, iType, iProgressListener, metadata, fields); + } + + @Override + public Set> getInvolvedIndexes(final Collection fields) { + return delegate.getInvolvedIndexes(fields); + } + + @Override + public Set> getInvolvedIndexes(final String... fields) { + return delegate.getInvolvedIndexes(fields); + } + + @Override + public Set> getClassInvolvedIndexes(final Collection fields) { + return delegate.getClassInvolvedIndexes(fields); + } + + @Override + public Set> getClassInvolvedIndexes(final String... fields) { + return delegate.getClassInvolvedIndexes(fields); + } + + @Override + public boolean areIndexed(final Collection fields) { + return delegate.areIndexed(fields); + } + + @Override + public boolean areIndexed(final String... fields) { + return delegate.areIndexed(fields); + } + + @Override + public OIndex getClassIndex(final String iName) { + return delegate.getClassIndex(iName); + } + + @Override + public Set> getClassIndexes() { + return delegate.getClassIndexes(); + } + + @Override + public void getClassIndexes(final Collection> indexes) { + delegate.getClassIndexes(indexes); + } + + @Override + public Set> getIndexes() { + return delegate.getIndexes(); + } + + @Override + public String getCustom(final String iName) { + return delegate.getCustom(iName); + } + + @Override + public OClass setCustom(final String iName, String iValue) { + delegate.setCustom(iName, iValue); + return this; + } + + @Override + public void removeCustom(final String iName) { + delegate.removeCustom(iName); + } + + @Override + public void clearCustom() { + delegate.clearCustom(); + } + + @Override + public Set getCustomKeys() { + return delegate.getCustomKeys(); + } + + @Override + public boolean hasClusterId(final int clusterId) { + return delegate.hasClusterId(clusterId); + } + + @Override + public boolean hasPolymorphicClusterId(final int clusterId) { + return delegate.hasPolymorphicClusterId(clusterId); + } + + @Override + public int compareTo(final OClass o) { + return delegate.compareTo(o); + } + + @Override + public float getClassOverSize() { + return delegate.getClassOverSize(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public boolean equals(final Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassImpl.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassImpl.java new file mode 100755 index 00000000000..f39e4fff48a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OClassImpl.java @@ -0,0 +1,2827 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OArrays; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.annotation.OBeforeSerialization; +import com.orientechnologies.orient.core.command.OCommandResultListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV; +import com.orientechnologies.orient.core.sharding.auto.OAutoShardingClusterSelectionStrategy; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.storage.*; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.type.ODocumentWrapper; +import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.*; +import java.util.concurrent.Callable; + +/** + * Schema Class implementation. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OClassImpl extends ODocumentWrapperNoClass implements OClass { + private static final long serialVersionUID = 1L; + private static final int NOT_EXISTENT_CLUSTER_ID = -1; + final OSchemaShared owner; + private final Map properties = new HashMap(); + private int defaultClusterId = NOT_EXISTENT_CLUSTER_ID; + private volatile String name; + private String description; + private int[] clusterIds; + private List superClasses = new ArrayList(); + private int[] polymorphicClusterIds; + private List subclasses; + private float overSize = 0f; + private String shortName; + private boolean strictMode = false; // @SINCE v1.0rc8 + private boolean abstractClass = false; // @SINCE v1.2.0 + private Map customFields; + private volatile OClusterSelectionStrategy clusterSelection; // @SINCE 1.7 + private volatile int hashCode; + + /** + * Constructor used in unmarshalling. + */ + protected OClassImpl(final OSchemaShared iOwner, final String iName) { + this(iOwner, new ODocument().setTrackingChanges(false), iName); + } + + protected OClassImpl(final OSchemaShared iOwner, final String iName, final int[] iClusterIds) { + this(iOwner, iName); + setClusterIds(iClusterIds); + defaultClusterId = iClusterIds[0]; + if (defaultClusterId == NOT_EXISTENT_CLUSTER_ID) + abstractClass = true; + + if (abstractClass) + setPolymorphicClusterIds(OCommonConst.EMPTY_INT_ARRAY); + else + setPolymorphicClusterIds(iClusterIds); + + clusterSelection = owner.getClusterSelectionFactory().newInstanceOfDefaultClass(); + } + + /** + * Constructor used in unmarshalling. + */ + protected OClassImpl(final OSchemaShared iOwner, final ODocument iDocument, final String iName) { + name = iName; + document = iDocument; + owner = iOwner; + } + + public static int[] readableClusters(final ODatabaseDocument iDatabase, final int[] iClusterIds) { + List listOfReadableIds = new ArrayList(); + + boolean all = true; + for (int clusterId : iClusterIds) { + try { + ////////// + // This will exclude (filter out) any specific classes without explicit read permission. + // getMetadata().getImmutableSchemaSnapshot()? + final OClass clazz = iDatabase.getMetadata().getSchema().getClassByClusterId(clusterId); + + if (clazz != null) + iDatabase.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, clazz.getName()); + ////////// + + final String clusterName = iDatabase.getClusterNameById(clusterId); + iDatabase.checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, clusterName); + listOfReadableIds.add(clusterId); + } catch (OSecurityAccessException securityException) { + all = false; + // if the cluster is inaccessible it's simply not processed in the list.add + } + } + + if (all) + // JUST RETURN INPUT ARRAY (FASTER) + return iClusterIds; + + final int[] readableClusterIds = new int[listOfReadableIds.size()]; + int index = 0; + for (int clusterId : listOfReadableIds) { + readableClusterIds[index++] = clusterId; + } + + return readableClusterIds; + } + + @Override + public OClusterSelectionStrategy getClusterSelection() { + acquireSchemaReadLock(); + try { + return clusterSelection; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OClass setClusterSelection(final OClusterSelectionStrategy clusterSelection) { + return setClusterSelection(clusterSelection.getName()); + } + + @Override + public OClass setClusterSelection(final String value) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` clusterselection '%s'", name, value); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` clusterselection '%s'", name, value); + OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + database.command(commandSQL).execute(); + + setClusterSelectionInternal(value); + } else + setClusterSelectionInternal(value); + + return this; + } finally { + releaseSchemaWriteLock(); + } + } + + @Override + public RET reload() { + return (RET) owner.reload(); + } + + public String getCustom(final String iName) { + acquireSchemaReadLock(); + try { + if (customFields == null) + return null; + + return customFields.get(iName); + } finally { + releaseSchemaReadLock(); + } + } + + public OClassImpl setCustom(final String name, final String value) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` custom %s=%s", getName(), name, value); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` custom %s=%s", getName(), name, value); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setCustomInternal(name, value); + } else + setCustomInternal(name, value); + + return this; + } finally { + releaseSchemaWriteLock(); + } + } + + public Map getCustomInternal() { + acquireSchemaReadLock(); + try { + if (customFields != null) + return Collections.unmodifiableMap(customFields); + return null; + } finally { + releaseSchemaReadLock(); + } + } + + public void removeCustom(final String name) { + setCustom(name, null); + } + + public void clearCustom() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` custom clear", getName()); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` custom clear", getName()); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + database.command(commandSQL).execute(); + + clearCustomInternal(); + } else + clearCustomInternal(); + + } finally { + releaseSchemaWriteLock(); + } + } + + public Set getCustomKeys() { + acquireSchemaReadLock(); + try { + if (customFields != null) + return Collections.unmodifiableSet(customFields.keySet()); + return new HashSet(); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public boolean hasClusterId(final int clusterId) { + return Arrays.binarySearch(clusterIds, clusterId) >= 0; + } + + @Override + public boolean hasPolymorphicClusterId(final int clusterId) { + return Arrays.binarySearch(polymorphicClusterIds, clusterId) >= 0; + } + + @Override + public OClass getSuperClass() { + acquireSchemaReadLock(); + try { + return superClasses.isEmpty() ? null : superClasses.get(0); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OClass setSuperClass(OClass iSuperClass) { + setSuperClasses(iSuperClass != null ? Arrays.asList(iSuperClass) : Collections.EMPTY_LIST); + return this; + } + + public String getName() { + return name; + } + + @Override + public List getSuperClasses() { + acquireSchemaReadLock(); + try { + return Collections.unmodifiableList((List) superClasses); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public boolean hasSuperClasses() { + acquireSchemaReadLock(); + try { + return !superClasses.isEmpty(); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public List getSuperClassesNames() { + acquireSchemaReadLock(); + try { + List superClassesNames = new ArrayList(superClasses.size()); + for (OClassImpl superClass : superClasses) { + superClassesNames.add(superClass.getName()); + } + return superClassesNames; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setSuperClassesByNames(List classNames) { + if (classNames == null) + classNames = Collections.EMPTY_LIST; + + final List classes = new ArrayList(classNames.size()); + final OSchema schema = getDatabase().getMetadata().getSchema(); + for (String className : classNames) { + classes.add(schema.getClass(decodeClassName(className))); + } + return setSuperClasses(classes); + } + + @Override + public OClass setSuperClasses(final List classes) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + if (classes != null) { + List toCheck = new ArrayList(classes); + toCheck.add(this); + checkParametersConflict(toCheck); + } + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + final StringBuilder sb = new StringBuilder(); + if (classes != null && classes.size() > 0) { + for (OClass superClass : classes) { + sb.append('`').append(superClass.getName()).append("`,"); + } + sb.deleteCharAt(sb.length() - 1); + } else + sb.append("null"); + + final String cmd = String.format("alter class `%s` superclasses %s", name, sb); + if (storage instanceof OStorageProxy) { + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setSuperClassesInternal(classes); + } else + setSuperClassesInternal(classes); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + void setSuperClassesInternal(final List classes) { + acquireSchemaWriteLock(); + try { + List newSuperClasses = new ArrayList(); + OClassImpl cls; + for (OClass superClass : classes) { + if (superClass instanceof OClassAbstractDelegate) + cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate; + else + cls = (OClassImpl) superClass; + + if (newSuperClasses.contains(cls)) { + throw new OSchemaException("Duplicated superclass '" + cls.getName() + "'"); + } + + newSuperClasses.add(cls); + } + + List toAddList = new ArrayList(newSuperClasses); + toAddList.removeAll(superClasses); + List toRemoveList = new ArrayList(superClasses); + toRemoveList.removeAll(newSuperClasses); + + for (OClassImpl toRemove : toRemoveList) { + toRemove.removeBaseClassInternal(this); + } + for (OClassImpl addTo : toAddList) { + addTo.addBaseClass(this); + } + superClasses.clear(); + superClasses.addAll(newSuperClasses); + } finally { + releaseSchemaWriteLock(); + } + } + + @Override + public OClass addSuperClass(final OClass superClass) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + checkParametersConflict(superClass); + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String + .format("alter class `%s` superclass +`%s`", name, superClass != null ? superClass.getName() : null); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String + .format("alter class `%s` superclass +`%s`", name, superClass != null ? superClass.getName() : null); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + addSuperClassInternal(superClass); + } else + addSuperClassInternal(superClass); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + void addSuperClassInternal(final OClass superClass) { + acquireSchemaWriteLock(); + try { + final OClassImpl cls; + + if (superClass instanceof OClassAbstractDelegate) + cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate; + else + cls = (OClassImpl) superClass; + + if (cls != null) { + + // CHECK THE USER HAS UPDATE PRIVILEGE AGAINST EXTENDING CLASS + final OSecurityUser user = getDatabase().getUser(); + if (user != null) + user.allow(ORule.ResourceGeneric.CLASS, cls.getName(), ORole.PERMISSION_UPDATE); + + if (superClasses.contains(superClass)) { + throw new OSchemaException( + "Class: '" + this.getName() + "' already has the class '" + superClass.getName() + "' as superclass"); + } + + cls.addBaseClass(this); + superClasses.add(cls); + } + } finally { + releaseSchemaWriteLock(); + } + } + + @Override + public OClass removeSuperClass(OClass superClass) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String + .format("alter class `%s` superclass -`%s`", name, superClass != null ? superClass.getName() : null); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String + .format("alter class `%s` superclass -`%s`", name, superClass != null ? superClass.getName() : null); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + removeSuperClassInternal(superClass); + } else + removeSuperClassInternal(superClass); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + void removeSuperClassInternal(final OClass superClass) { + acquireSchemaWriteLock(); + try { + final OClassImpl cls; + + if (superClass instanceof OClassAbstractDelegate) + cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate; + else + cls = (OClassImpl) superClass; + + if (superClasses.contains(cls)) { + if (cls != null) + cls.removeBaseClassInternal(this); + + superClasses.remove(superClass); + } + } finally { + releaseSchemaWriteLock(); + } + } + + public OClass setName(final String name) { + if (getName().equals(name)) + return this; + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + final Character wrongCharacter = OSchemaShared.checkClassNameIfValid(name); + OClass oClass = getDatabase().getMetadata().getSchema().getClass(name); + if (oClass != null) { + String error = String.format("Cannot rename class %s to %s. A Class with name %s exists", this.name, name, name); + throw new OSchemaException(error); + } + if (wrongCharacter != null) + throw new OSchemaException( + "Invalid class name found. Character '" + wrongCharacter + "' cannot be used in class name '" + name + "'"); + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` name `%s`", this.name, name); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` name `%s`", this.name, name); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setNameInternal(name); + } else + setNameInternal(name); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public long getSize() { + acquireSchemaReadLock(); + try { + long size = 0; + for (int clusterId : clusterIds) + size += getDatabase().getClusterRecordSizeById(clusterId); + + return size; + } finally { + releaseSchemaReadLock(); + } + } + + public String getShortName() { + acquireSchemaReadLock(); + try { + return shortName; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setShortName(String shortName) { + if (shortName != null) { + shortName = shortName.trim(); + if (shortName.isEmpty()) + shortName = null; + } + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` shortname %s", name, shortName); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + + final String cmd = String.format("alter class `%s` shortname %s", name, shortName); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setShortNameInternal(shortName); + } else + setShortNameInternal(shortName); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public String getDescription() { + acquireSchemaReadLock(); + try { + return description; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setDescription(String iDescription) { + if (iDescription != null) { + iDescription = iDescription.trim(); + if (iDescription.isEmpty()) + iDescription = null; + } + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` description ?", name); + database.command(new OCommandSQL(cmd)).execute(iDescription); + } else if (isDistributedCommand()) { + + final String cmd = String.format("alter class `%s` description ?", name); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(iDescription); + setDescriptionInternal(iDescription); + } else + setDescriptionInternal(iDescription); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public String getStreamableName() { + acquireSchemaReadLock(); + try { + return shortName != null ? shortName : name; + } finally { + releaseSchemaReadLock(); + } + } + + public Collection declaredProperties() { + acquireSchemaReadLock(); + try { + return Collections.unmodifiableCollection(properties.values()); + } finally { + releaseSchemaReadLock(); + } + } + + public Map propertiesMap() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + acquireSchemaReadLock(); + try { + final Map props = new HashMap(20); + propertiesMap(props, true); + return props; + } finally { + releaseSchemaReadLock(); + } + } + + private void propertiesMap(Map propertiesMap, boolean keepCase) { + for (OProperty p : properties.values()) { + String propName = p.getName(); + if (!keepCase) + propName = propName.toLowerCase(Locale.ENGLISH); + if (!propertiesMap.containsKey(propName)) + propertiesMap.put(propName, p); + } + for (OClassImpl superClass : superClasses) { + superClass.propertiesMap(propertiesMap, keepCase); + } + } + + public Collection properties() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + acquireSchemaReadLock(); + try { + final Collection props = new ArrayList(); + properties(props); + return props; + } finally { + releaseSchemaReadLock(); + } + } + + private void properties(Collection properties) { + properties.addAll(this.properties.values()); + for (OClassImpl superClass : superClasses) { + superClass.properties(properties); + } + } + + public void getIndexedProperties(Collection indexedProperties) { + for (OProperty p : properties.values()) + if (areIndexed(p.getName())) + indexedProperties.add(p); + for (OClassImpl superClass : superClasses) { + superClass.getIndexedProperties(indexedProperties); + } + } + + @Override + public Collection getIndexedProperties() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + acquireSchemaReadLock(); + try { + Collection indexedProps = new HashSet(); + getIndexedProperties(indexedProps); + return indexedProps; + } finally { + releaseSchemaReadLock(); + } + } + + public OProperty getProperty(String propertyName) { + acquireSchemaReadLock(); + try { + propertyName = propertyName.toLowerCase(Locale.ENGLISH); + + OProperty p = properties.get(propertyName); + if (p != null) + return p; + for (int i = 0; i < superClasses.size() && p == null; i++) { + p = superClasses.get(i).getProperty(propertyName); + } + return p; + } finally { + releaseSchemaReadLock(); + } + } + + public OProperty createProperty(final String iPropertyName, final OType iType) { + return addProperty(iPropertyName, iType, null, null, false); + } + + public OProperty createProperty(final String iPropertyName, final OType iType, final OClass iLinkedClass) { + return addProperty(iPropertyName, iType, null, iLinkedClass, false); + } + + public OProperty createProperty(final String iPropertyName, final OType iType, final OClass iLinkedClass, final boolean unsafe) { + return addProperty(iPropertyName, iType, null, iLinkedClass, unsafe); + } + + public OProperty createProperty(final String iPropertyName, final OType iType, final OType iLinkedType) { + return addProperty(iPropertyName, iType, iLinkedType, null, false); + } + + public OProperty createProperty(final String iPropertyName, final OType iType, final OType iLinkedType, final boolean unsafe) { + return addProperty(iPropertyName, iType, iLinkedType, null, unsafe); + } + + @Override + public boolean existsProperty(String propertyName) { + acquireSchemaReadLock(); + try { + propertyName = propertyName.toLowerCase(Locale.ENGLISH); + boolean result = properties.containsKey(propertyName); + if (result) + return true; + for (OClassImpl superClass : superClasses) { + result = superClass.existsProperty(propertyName); + if (result) + return true; + } + return false; + } finally { + releaseSchemaReadLock(); + } + } + + public void dropProperty(final String propertyName) { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot drop a property inside a transaction"); + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + final String lowerName = propertyName.toLowerCase(Locale.ENGLISH); + + acquireSchemaWriteLock(); + try { + if (!properties.containsKey(lowerName)) + throw new OSchemaException("Property '" + propertyName + "' not found in class " + name + "'"); + + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + if (storage instanceof OStorageProxy) { + if (getDatabase().getStorage().getConfiguration().isStrictSql()) { + database.command(new OCommandSQL("drop property " + name + ".`" + propertyName + "`")).execute(); + } else { + database.command(new OCommandSQL("drop property " + name + '.' + propertyName)).execute(); + } + } else if (isDistributedCommand()) { + OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public OProperty call() throws Exception { + dropPropertyInternal(propertyName); + return null; + } + }); + + String stm; + if (getDatabase().getStorage().getConfiguration().isStrictSql()) { + stm = "drop property " + name + ".`" + propertyName + "`"; + } else { + stm = "drop property " + name + "." + propertyName; + } + final OCommandSQL commandSQL = new OCommandSQL(stm); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + } else + OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public OProperty call() throws Exception { + dropPropertyInternal(propertyName); + return null; + } + }); + + } finally { + releaseSchemaWriteLock(); + } + } + + @Override + public void fromStream() { + subclasses = null; + superClasses.clear(); + + name = document.field("name"); + if (document.containsField("shortName")) + shortName = document.field("shortName"); + else + shortName = null; + if (document.containsField("description")) + description = document.field("description"); + else + description = null; + defaultClusterId = document.field("defaultClusterId"); + if (document.containsField("strictMode")) + strictMode = document.field("strictMode"); + else + strictMode = false; + + if (document.containsField("abstract")) + abstractClass = document.field("abstract"); + else + abstractClass = false; + + if (document.field("overSize") != null) + overSize = document.field("overSize"); + else + overSize = 0f; + + final Object cc = document.field("clusterIds"); + if (cc instanceof Collection) { + final Collection coll = document.field("clusterIds"); + clusterIds = new int[coll.size()]; + int i = 0; + for (final Integer item : coll) + clusterIds[i++] = item; + } else + clusterIds = (int[]) cc; + Arrays.sort(clusterIds); + + if (clusterIds.length == 1 && clusterIds[0] == -1) + setPolymorphicClusterIds(OCommonConst.EMPTY_INT_ARRAY); + else + setPolymorphicClusterIds(clusterIds); + + // READ PROPERTIES + OPropertyImpl prop; + + final Map newProperties = new HashMap(); + final Collection storedProperties = document.field("properties"); + + if (storedProperties != null) + for (OIdentifiable id : storedProperties) { + ODocument p = id.getRecord(); + prop = new OPropertyImpl(this, p); + prop.fromStream(); + + if (properties.containsKey(prop.getName())) { + prop = (OPropertyImpl) properties.get(prop.getName().toLowerCase(Locale.ENGLISH)); + prop.fromStream(p); + } + + newProperties.put(prop.getName().toLowerCase(Locale.ENGLISH), prop); + } + + properties.clear(); + properties.putAll(newProperties); + customFields = document.field("customFields", OType.EMBEDDEDMAP); + clusterSelection = owner.getClusterSelectionFactory().getStrategy((String) document.field("clusterSelection")); + } + + @Override + @OBeforeSerialization + public ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + + try { + document.field("name", name); + document.field("shortName", shortName); + document.field("description", description); + document.field("defaultClusterId", defaultClusterId); + document.field("clusterIds", clusterIds); + document.field("clusterSelection", clusterSelection.getName()); + document.field("overSize", overSize); + document.field("strictMode", strictMode); + document.field("abstract", abstractClass); + + final Set props = new LinkedHashSet(); + for (final OProperty p : properties.values()) { + props.add(((OPropertyImpl) p).toStream()); + } + document.field("properties", props, OType.EMBEDDEDSET); + + if (superClasses.isEmpty()) { + // Single super class is deprecated! + document.field("superClass", null, OType.STRING); + document.field("superClasses", null, OType.EMBEDDEDLIST); + } else { + // Single super class is deprecated! + document.field("superClass", superClasses.get(0).getName(), OType.STRING); + List superClassesNames = new ArrayList(); + for (OClassImpl superClass : superClasses) { + superClassesNames.add(superClass.getName()); + } + document.field("superClasses", superClassesNames, OType.EMBEDDEDLIST); + } + + document.field("customFields", customFields != null && customFields.size() > 0 ? customFields : null, OType.EMBEDDEDMAP); + + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + + return document; + } + + @Override + public int getClusterForNewInstance(final ODocument doc) { + acquireSchemaReadLock(); + try { + return clusterSelection.getCluster(this, doc); + } finally { + releaseSchemaReadLock(); + } + } + + public int getDefaultClusterId() { + acquireSchemaReadLock(); + try { + return defaultClusterId; + } finally { + releaseSchemaReadLock(); + } + } + + public void setDefaultClusterId(final int defaultClusterId) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + this.defaultClusterId = defaultClusterId; + } finally { + releaseSchemaWriteLock(); + } + } + + public int[] getClusterIds() { + acquireSchemaReadLock(); + try { + return clusterIds; + } finally { + releaseSchemaReadLock(); + } + } + + public int[] getPolymorphicClusterIds() { + acquireSchemaReadLock(); + try { + return Arrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length); + } finally { + releaseSchemaReadLock(); + } + } + + private void setPolymorphicClusterIds(final int[] iClusterIds) { + Set set = new TreeSet(); + for (int iClusterId : iClusterIds) { + set.add(iClusterId); + } + polymorphicClusterIds = new int[set.size()]; + int i = 0; + for (Integer clusterId : set) { + polymorphicClusterIds[i] = clusterId; + i++; + } + } + + public void renameProperty(final String iOldName, final String iNewName) { + final OProperty p = properties.remove(iOldName.toLowerCase(Locale.ENGLISH)); + if (p != null) + properties.put(iNewName.toLowerCase(Locale.ENGLISH), p); + } + + public OClass addClusterId(final int clusterId) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + if (isAbstract()) { + throw new OSchemaException("Impossible to associate a cluster to an abstract class class"); + } + + acquireSchemaWriteLock(); + try { + + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` addcluster %d", name, clusterId); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + + final String cmd = String.format("alter class `%s` addcluster %d", name, clusterId); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + addClusterIdInternal(clusterId); + } else + addClusterIdInternal(clusterId); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + public static OClass addClusters(final OClass cls, final int iClusters) { + final String clusterBase = cls.getName().toLowerCase(Locale.ENGLISH) + "_"; + for (int i = 1; i < iClusters; ++i) { + cls.addCluster(clusterBase + i); + } + return cls; + } + + @Override + public OClass addCluster(final String clusterNameOrId) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + if (isAbstract()) { + throw new OSchemaException("Impossible to associate a cluster to an abstract class class"); + } + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` addcluster `%s`", name, clusterNameOrId); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final int clusterId = owner.createClusterIfNeeded(clusterNameOrId); + addClusterIdInternal(clusterId); + + final String cmd = String.format("alter class `%s` addcluster `%s`", name, clusterNameOrId); + + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + } else { + final int clusterId = owner.createClusterIfNeeded(clusterNameOrId); + addClusterIdInternal(clusterId); + } + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public OClass truncateCluster(String clusterName) { + getDatabase().checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_DELETE, name); + + acquireSchemaReadLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + if (storage instanceof OStorageProxy) { + final String cmd = String.format("truncate cluster %s", clusterName); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("truncate cluster %s", clusterName); + + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + truncateClusterInternal(clusterName, storage); + } else + truncateClusterInternal(clusterName, storage); + } finally { + releaseSchemaReadLock(); + } + + return this; + } + + private void truncateClusterInternal(final String clusterName, final OStorage storage) { + final OCluster cluster = storage.getClusterByName(clusterName); + + if (cluster == null) { + throw new ODatabaseException("Cluster with name " + clusterName + " does not exist"); + } + + try { + storage.checkForClusterPermissions(clusterName); + cluster.truncate(); + } catch (IOException e) { + throw OException.wrapException(new ODatabaseException("Error during truncate of cluster " + clusterName), e); + } + + for (OIndex index : getIndexes()) { + index.rebuild(); + } + } + + public OClass removeClusterId(final int clusterId) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + if (clusterIds.length == 1 && clusterId == clusterIds[0]) + throw new ODatabaseException(" Impossible to remove the last cluster of class '" + getName() + "' drop the class instead"); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` removecluster %d", name, clusterId); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` removecluster %d", name, clusterId); + + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + removeClusterIdInternal(clusterId); + } else + removeClusterIdInternal(clusterId); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public Collection getSubclasses() { + acquireSchemaReadLock(); + try { + if (subclasses == null || subclasses.size() == 0) + return Collections.emptyList(); + + return Collections.unmodifiableCollection(subclasses); + } finally { + releaseSchemaReadLock(); + } + } + + public Collection getAllSubclasses() { + acquireSchemaReadLock(); + try { + final Set set = new HashSet(); + if (subclasses != null) { + set.addAll(subclasses); + + for (OClass c : subclasses) + set.addAll(c.getAllSubclasses()); + } + return set; + } finally { + releaseSchemaReadLock(); + } + } + + public Collection getBaseClasses() { + return getSubclasses(); + } + + public Collection getAllBaseClasses() { + return getAllSubclasses(); + } + + @Override + public Collection getAllSuperClasses() { + Set ret = new HashSet(); + getAllSuperClasses(ret); + return ret; + } + + private void getAllSuperClasses(Set set) { + set.addAll(superClasses); + for (OClassImpl superClass : superClasses) { + superClass.getAllSuperClasses(set); + } + } + + OClass removeBaseClassInternal(final OClass baseClass) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + if (subclasses == null) + return this; + + if (subclasses.remove(baseClass)) + removePolymorphicClusterIds((OClassImpl) baseClass); + + return this; + } finally { + releaseSchemaWriteLock(); + } + } + + public float getOverSize() { + acquireSchemaReadLock(); + try { + if (overSize > 0) + // CUSTOM OVERSIZE SET + return overSize; + + // NO OVERSIZE by default + float maxOverSize = 0; + float thisOverSize; + for (OClassImpl superClass : superClasses) { + thisOverSize = superClass.getOverSize(); + if (thisOverSize > maxOverSize) + maxOverSize = thisOverSize; + } + return maxOverSize; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setOverSize(final float overSize) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + // FORMAT FLOAT LOCALE AGNOSTIC + final String cmd = String.format("alter class `%s` oversize %s", name, new Float(overSize).toString()); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + // FORMAT FLOAT LOCALE AGNOSTIC + final String cmd = String.format("alter class `%s` oversize %s", name, new Float(overSize).toString()); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setOverSizeInternal(overSize); + } else + setOverSizeInternal(overSize); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + @Override + public float getClassOverSize() { + acquireSchemaReadLock(); + try { + return overSize; + } finally { + releaseSchemaReadLock(); + } + } + + public boolean isAbstract() { + acquireSchemaReadLock(); + try { + return abstractClass; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setAbstract(boolean isAbstract) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` abstract %s", name, isAbstract); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` abstract %s", name, isAbstract); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setAbstractInternal(isAbstract); + } else + setAbstractInternal(isAbstract); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public boolean isStrictMode() { + acquireSchemaReadLock(); + try { + return strictMode; + } finally { + releaseSchemaReadLock(); + } + } + + public OClass setStrictMode(final boolean isStrict) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` strictmode %s", name, isStrict); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter class `%s` strictmode %s", name, isStrict); + + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setStrictModeInternal(isStrict); + } else + setStrictModeInternal(isStrict); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object obj) { + acquireSchemaReadLock(); + try { + if (this == obj) + return true; + if (obj == null) + return false; + if (!OClass.class.isAssignableFrom(obj.getClass())) + return false; + final OClass other = (OClass) obj; + if (name == null) { + if (other.getName() != null) + return false; + } else if (!name.equals(other.getName())) + return false; + + return true; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public int hashCode() { + int sh = hashCode; + if (sh != 0) + return sh; + + acquireSchemaReadLock(); + try { + sh = hashCode; + if (sh != 0) + return sh; + + calculateHashCode(); + return hashCode; + } finally { + releaseSchemaReadLock(); + } + } + + public int compareTo(final OClass o) { + acquireSchemaReadLock(); + try { + return name.compareTo(o.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + public long count() { + return count(true); + } + + public long count(final boolean isPolymorphic) { + acquireSchemaReadLock(); + try { + if (isPolymorphic) + return getDatabase().countClusterElements(readableClusters(getDatabase(), polymorphicClusterIds)); + + return getDatabase().countClusterElements(readableClusters(getDatabase(), clusterIds)); + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Truncates all the clusters the class uses. + * + * @throws IOException + */ + public void truncate() throws IOException { + + getDatabase().checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_UPDATE); + + if (isSubClassOf(OSecurityShared.RESTRICTED_CLASSNAME)) { + throw new OSecurityException( + "Class '" + getName() + "' cannot be truncated because has record level security enabled (extends '" + + OSecurityShared.RESTRICTED_CLASSNAME + "')"); + } + + final OStorage storage = getDatabase().getStorage(); + acquireSchemaReadLock(); + try { + + for (int id : clusterIds) { + OCluster cl = storage.getClusterById(id); + storage.checkForClusterPermissions(cl.getName()); + cl.truncate(); + } + for (OIndex index : getClassIndexes()) + index.clear(); + + Set> superclassIndexes = new HashSet>(); + superclassIndexes.addAll(getIndexes()); + superclassIndexes.removeAll(getClassIndexes()); + for (OIndex index : superclassIndexes) { + index.rebuild(); + } + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Check if the current instance extends specified schema class. + * + * @param iClassName of class that should be checked + * + * @return Returns true if the current instance extends the passed schema class (iClass) + * + * @see #isSuperClassOf(OClass) + */ + public boolean isSubClassOf(final String iClassName) { + acquireSchemaReadLock(); + try { + if (iClassName == null) + return false; + + if (iClassName.equalsIgnoreCase(getName()) || iClassName.equalsIgnoreCase(getShortName())) + return true; + for (OClassImpl superClass : superClasses) { + if (superClass.isSubClassOf(iClassName)) + return true; + } + return false; + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Check if the current instance extends specified schema class. + * + * @param clazz to check + * + * @return true if the current instance extends the passed schema class (iClass) + * + * @see #isSuperClassOf(OClass) + */ + public boolean isSubClassOf(final OClass clazz) { + acquireSchemaReadLock(); + try { + if (clazz == null) + return false; + if (equals(clazz)) + return true; + for (OClassImpl superClass : superClasses) { + if (superClass.isSubClassOf(clazz)) + return true; + } + return false; + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Returns true if the passed schema class (iClass) extends the current instance. + * + * @param clazz to check + * + * @return Returns true if the passed schema class extends the current instance + * + * @see #isSubClassOf(OClass) + */ + public boolean isSuperClassOf(final OClass clazz) { + return clazz != null && clazz.isSubClassOf(this); + } + + public Object get(final ATTRIBUTES iAttribute) { + if (iAttribute == null) + throw new IllegalArgumentException("attribute is null"); + + switch (iAttribute) { + case NAME: + return getName(); + case SHORTNAME: + return getShortName(); + case SUPERCLASS: + return getSuperClass(); + case SUPERCLASSES: + return getSuperClasses(); + case OVERSIZE: + return getOverSize(); + case STRICTMODE: + return isStrictMode(); + case ABSTRACT: + return isAbstract(); + case CLUSTERSELECTION: + return getClusterSelection(); + case CUSTOM: + return getCustomInternal(); + case DESCRIPTION: + return getDescription(); + } + + throw new IllegalArgumentException("Cannot find attribute '" + iAttribute + "'"); + } + + public OClass set(final ATTRIBUTES attribute, final Object iValue) { + if (attribute == null) + throw new IllegalArgumentException("attribute is null"); + + final String stringValue = iValue != null ? iValue.toString() : null; + final boolean isNull = stringValue == null || stringValue.equalsIgnoreCase("NULL"); + + switch (attribute) { + case NAME: + setName(decodeClassName(stringValue)); + break; + case SHORTNAME: + setShortName(decodeClassName(stringValue)); + break; + case SUPERCLASS: + if (stringValue == null) + throw new IllegalArgumentException("Superclass is null"); + + if (stringValue.startsWith("+")) { + addSuperClass(getDatabase().getMetadata().getSchema().getClass(decodeClassName(stringValue.substring(1)))); + } else if (stringValue.startsWith("-")) { + removeSuperClass(getDatabase().getMetadata().getSchema().getClass(decodeClassName(stringValue.substring(1)))); + } else { + setSuperClass(getDatabase().getMetadata().getSchema().getClass(decodeClassName(stringValue))); + } + break; + case SUPERCLASSES: + setSuperClassesByNames(stringValue != null ? Arrays.asList(stringValue.split(",\\s*")) : null); + break; + case OVERSIZE: + setOverSize(Float.parseFloat(stringValue)); + break; + case STRICTMODE: + setStrictMode(Boolean.parseBoolean(stringValue)); + break; + case ABSTRACT: + setAbstract(Boolean.parseBoolean(stringValue)); + break; + case ADDCLUSTER: { + addCluster(stringValue); + break; + } + case REMOVECLUSTER: + int clId = owner.getClusterId(stringValue); + if (clId == NOT_EXISTENT_CLUSTER_ID) + throw new IllegalArgumentException("Cluster id '" + stringValue + "' cannot be removed"); + removeClusterId(clId); + break; + case CLUSTERSELECTION: + setClusterSelection(stringValue); + break; + case CUSTOM: + int indx = stringValue != null ? stringValue.indexOf('=') : -1; + if (indx < 0) { + if (isNull || "clear".equalsIgnoreCase(stringValue)) { + clearCustom(); + } else + throw new IllegalArgumentException("Syntax error: expected = or clear, instead found: " + iValue); + } else { + String customName = stringValue.substring(0, indx).trim(); + String customValue = stringValue.substring(indx + 1).trim(); + if (isQuoted(customValue)) { + customValue = removeQuotes(customValue); + } + if (customValue.isEmpty()) + removeCustom(customName); + else + setCustom(customName, customValue); + } + break; + case DESCRIPTION: + setDescription(stringValue); + break; + case ENCRYPTION: + setEncryption(stringValue); + break; + } + return this; + } + + private String removeQuotes(String s) { + s = s.trim(); + StringBuilder result = new StringBuilder(); + boolean escaping = false; + for (int i = 1; i < s.length() - 1; i++) { + char c = s.charAt(i); + if (escaping) { + result.append(c); + escaping = false; + continue; + } + if (c == '\\') { + escaping = true; + continue; + } + result.append(c); + } + return result.toString(); + } + + private boolean isQuoted(String s) { + s = s.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) + return true; + if (s.startsWith("'") && s.endsWith("'")) + return true; + if (s.startsWith("`") && s.endsWith("`")) + return true; + + return false; + } + + public OClassImpl setEncryption(final String iValue) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter class `%s` encryption %s", name, iValue); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + + final String cmd = String.format("alter class `%s` encryption %s", name, iValue); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + setEncryptionInternal(iValue); + } else + setEncryptionInternal(iValue); + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + protected void setEncryptionInternal(final String iValue) { + for (int cl : getClusterIds()) { + final OCluster c = getDatabase().getStorage().getClusterById(cl); + if (c != null) + try { + c.set(OCluster.ATTRIBUTES.ENCRYPTION, iValue); + } catch (IOException e) { + } + } + } + + public OPropertyImpl addPropertyInternal(final String name, final OType type, final OType linkedType, final OClass linkedClass, + final boolean unsafe) { + if (name == null || name.length() == 0) + throw new OSchemaException("Found property name null"); + + final Character wrongCharacter = OSchemaShared.checkFieldNameIfValid(name); + if (wrongCharacter != null) + throw new OSchemaException("Invalid property name '" + name + "'. Character '" + wrongCharacter + "' cannot be used"); + + if (!unsafe) + checkPersistentPropertyType(getDatabase(), name, type); + + final String lowerName = name.toLowerCase(Locale.ENGLISH); + + final OPropertyImpl prop; + + // This check are doubled becouse used by sql commands + if (linkedType != null) + OPropertyImpl.checkLinkTypeSupport(type); + + if (linkedClass != null) + OPropertyImpl.checkSupportLinkedClass(type); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + if (properties.containsKey(lowerName)) + throw new OSchemaException("Class '" + this.name + "' already has property '" + name + "'"); + + OGlobalProperty global = owner.findOrCreateGlobalProperty(name, type); + + prop = new OPropertyImpl(this, global); + + properties.put(lowerName, prop); + + if (linkedType != null) + prop.setLinkedTypeInternal(linkedType); + else if (linkedClass != null) + prop.setLinkedClassInternal(linkedClass); + } finally { + releaseSchemaWriteLock(); + } + + if (prop != null && !unsafe) + fireDatabaseMigration(getDatabase(), name, type); + + return prop; + } + + public OIndex createIndex(final String iName, final INDEX_TYPE iType, final String... fields) { + return createIndex(iName, iType.name(), fields); + } + + public OIndex createIndex(final String iName, final String iType, final String... fields) { + return createIndex(iName, iType, null, null, fields); + } + + public OIndex createIndex(final String iName, final INDEX_TYPE iType, final OProgressListener iProgressListener, + final String... fields) { + return createIndex(iName, iType.name(), iProgressListener, null, fields); + } + + public OIndex createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata, + String... fields) { + return createIndex(iName, iType, iProgressListener, metadata, null, fields); + } + + public OIndex createIndex(final String name, String type, final OProgressListener progressListener, ODocument metadata, + String algorithm, final String... fields) { + if (type == null) + throw new IllegalArgumentException("Index type is null"); + + type = type.toUpperCase(Locale.ENGLISH); + + if (fields.length == 0) { + throw new OIndexException("List of fields to index cannot be empty."); + } + + final String localName = this.name; + final int[] localPolymorphicClusterIds = polymorphicClusterIds; + + for (final String fieldToIndex : fields) { + final String fieldName = decodeClassName(OIndexDefinitionFactory.extractFieldName(fieldToIndex)); + + if (!fieldName.equals("@rid") && !existsProperty(fieldName)) + throw new OIndexException( + "Index with name '" + name + "' cannot be created on class '" + localName + "' because the field '" + fieldName + + "' is absent in class definition"); + } + + final OIndexDefinition indexDefinition = OIndexDefinitionFactory + .createIndexDefinition(this, Arrays.asList(fields), extractFieldTypes(fields), null, type, algorithm); + + return getDatabase().getMetadata().getIndexManager() + .createIndex(name, type, indexDefinition, localPolymorphicClusterIds, progressListener, metadata, algorithm); + } + + public boolean areIndexed(final String... fields) { + return areIndexed(Arrays.asList(fields)); + } + + public boolean areIndexed(final Collection fields) { + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + + acquireSchemaReadLock(); + try { + final boolean currentClassResult = indexManager.areIndexed(name, fields); + + if (currentClassResult) + return true; + for (OClassImpl superClass : superClasses) { + if (superClass.areIndexed(fields)) + return true; + } + return false; + } finally { + releaseSchemaReadLock(); + } + } + + public Set> getInvolvedIndexes(final String... fields) { + return getInvolvedIndexes(Arrays.asList(fields)); + } + + public Set> getInvolvedIndexes(final Collection fields) { + acquireSchemaReadLock(); + try { + final Set> result = new HashSet>(getClassInvolvedIndexes(fields)); + + for (OClassImpl superClass : superClasses) { + result.addAll(superClass.getInvolvedIndexes(fields)); + } + + return result; + } finally { + releaseSchemaReadLock(); + } + } + + public Set> getClassInvolvedIndexes(final Collection fields) { + + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + + acquireSchemaReadLock(); + try { + return indexManager.getClassInvolvedIndexes(name, fields); + } finally { + releaseSchemaReadLock(); + } + } + + public Set> getClassInvolvedIndexes(final String... fields) { + return getClassInvolvedIndexes(Arrays.asList(fields)); + } + + public OIndex getClassIndex(final String name) { + acquireSchemaReadLock(); + try { + return getDatabase().getMetadata().getIndexManager().getClassIndex(this.name, name); + } finally { + releaseSchemaReadLock(); + } + } + + public Set> getClassIndexes() { + acquireSchemaReadLock(); + try { + final OIndexManagerProxy idxManager = getDatabase().getMetadata().getIndexManager(); + if (idxManager == null) + return new HashSet>(); + + return idxManager.getClassIndexes(name); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public void getClassIndexes(final Collection> indexes) { + acquireSchemaReadLock(); + try { + final OIndexManagerProxy idxManager = getDatabase().getMetadata().getIndexManager(); + if (idxManager == null) + return; + + idxManager.getClassIndexes(name, indexes); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OIndex getAutoShardingIndex() { + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + return db != null ? db.getMetadata().getIndexManager().getClassAutoShardingIndex(name) : null; + } + + @Override + public boolean isEdgeType() { + return isSubClassOf(EDGE_CLASS_NAME); + } + + @Override + public boolean isVertexType() { + return isSubClassOf(VERTEX_CLASS_NAME); + } + + public void onPostIndexManagement() { + final OIndex autoShardingIndex = getAutoShardingIndex(); + if (autoShardingIndex != null) { + if (!getDatabase().getStorage().isRemote()) { + // OVERRIDE CLUSTER SELECTION + acquireSchemaWriteLock(); + try { + this.clusterSelection = new OAutoShardingClusterSelectionStrategy(this, autoShardingIndex); + } finally { + releaseSchemaWriteLock(); + } + } + } else if (clusterSelection instanceof OAutoShardingClusterSelectionStrategy) { + // REMOVE AUTO SHARDING CLUSTER SELECTION + acquireSchemaWriteLock(); + try { + this.clusterSelection = owner.getClusterSelectionFactory().newInstanceOfDefaultClass(); + } finally { + releaseSchemaWriteLock(); + } + } + } + + @Override + public void getIndexes(final Collection> indexes) { + acquireSchemaReadLock(); + try { + getClassIndexes(indexes); + for (OClass superClass : superClasses) { + superClass.getIndexes(indexes); + } + } finally { + releaseSchemaReadLock(); + } + } + + public Set> getIndexes() { + final Set> indexes = new HashSet>(); + getIndexes(indexes); + return indexes; + } + + public void acquireSchemaReadLock() { + owner.acquireSchemaReadLock(); + } + + public void releaseSchemaReadLock() { + owner.releaseSchemaReadLock(); + } + + public void acquireSchemaWriteLock() { + owner.acquireSchemaWriteLock(); + } + + public void releaseSchemaWriteLock() { + releaseSchemaWriteLock(true); + } + + public void releaseSchemaWriteLock(final boolean iSave) { + calculateHashCode(); + owner.releaseSchemaWriteLock(iSave); + } + + public void checkEmbedded() { + owner.checkEmbedded(getDatabase().getStorage().getUnderlying().getUnderlying()); + } + + public void setClusterSelectionInternal(final String clusterSelection) { + // AVOID TO CHECK THIS IN LOCK TO AVOID RE-GENERATION OF IMMUTABLE SCHEMAS + if (this.clusterSelection.getName().equals(clusterSelection)) + // NO CHANGES + return; + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.clusterSelection = owner.getClusterSelectionFactory().newInstance(clusterSelection); + } finally { + releaseSchemaWriteLock(); + } + } + + public void setClusterSelectionInternal(final OClusterSelectionStrategy iClusterSelection) { + // AVOID TO CHECK THIS IN LOCK TO AVOID RE-GENERATION OF IMMUTABLE SCHEMAS + if (this.clusterSelection.getClass().equals(iClusterSelection.getClass())) + // NO CHANGES + return; + + acquireSchemaWriteLock(); + try { + + // DON'T GET THE SCHEMA LOCK BECAUSE THIS CHANGE IS USED ONLY TO WRAP THE SELECTION STRATEGY + checkEmbedded(); + this.clusterSelection = iClusterSelection; + + } finally { + releaseSchemaWriteLock(false); + } + } + + public void fireDatabaseMigration(final ODatabaseDocument database, final String propertyName, final OType type) { + final boolean strictSQL = ((ODatabaseInternal) database).getStorage().getConfiguration().isStrictSql(); + + database.query(new OSQLAsynchQuery( + "select from " + getEscapedName(name, strictSQL) + " where " + getEscapedName(propertyName, strictSQL) + ".type() <> \"" + + type.name() + "\"", new OCommandResultListener() { + + @Override + public boolean result(Object iRecord) { + final ODocument record = ((OIdentifiable) iRecord).getRecord(); + record.field(propertyName, record.field(propertyName), type); + database.save(record); + return true; + } + + @Override + public void end() { + } + + @Override + public Object getResult() { + return null; + } + })); + } + + public void firePropertyNameMigration(final ODatabaseDocument database, final String propertyName, final String newPropertyName, + final OType type) { + final boolean strictSQL = ((ODatabaseInternal) database).getStorage().getConfiguration().isStrictSql(); + + database.query(new OSQLAsynchQuery( + "select from " + getEscapedName(name, strictSQL) + " where " + getEscapedName(propertyName, strictSQL) + " is not null ", + new OCommandResultListener() { + + @Override + public boolean result(Object iRecord) { + final ODocument record = ((OIdentifiable) iRecord).getRecord(); + record.setFieldType(propertyName, type); + record.field(newPropertyName, record.field(propertyName), type); + database.save(record); + return true; + } + + @Override + public void end() { + } + + @Override + public Object getResult() { + return null; + } + })); + + } + + public void checkPersistentPropertyType(final ODatabaseInternal database, final String propertyName, final OType type) { + final boolean strictSQL = database.getStorage().getConfiguration().isStrictSql(); + + final StringBuilder builder = new StringBuilder(256); + builder.append("select count(*) from "); + builder.append(getEscapedName(name, strictSQL)); + builder.append(" where "); + builder.append(getEscapedName(propertyName, strictSQL)); + builder.append(".type() not in ["); + + final Iterator cur = type.getCastable().iterator(); + while (cur.hasNext()) { + builder.append('"').append(cur.next().name()).append('"'); + if (cur.hasNext()) + builder.append(","); + } + builder.append("] and ").append(getEscapedName(propertyName, strictSQL)).append(" is not null "); + if (type.isMultiValue()) + builder.append(" and ").append(getEscapedName(propertyName, strictSQL)).append(".size() <> 0 limit 1"); + + final List res = database.command(new OCommandSQL(builder.toString())).execute(); + if (((Long) res.get(0).field("count")) > 0) + throw new OSchemaException("The database contains some schema-less data in the property '" + name + "." + propertyName + + "' that is not compatible with the type " + type + ". Fix those records and change the schema again"); + } + + protected String getEscapedName(final String iName, final boolean iStrictSQL) { + if (iStrictSQL) + // ESCAPE NAME + return "`" + iName + "`"; + return iName; + } + + public OSchemaShared getOwner() { + return owner; + } + + private void calculateHashCode() { + int result = super.hashCode(); + result = 31 * result + (name != null ? name.hashCode() : 0); + hashCode = result; + } + + private void setOverSizeInternal(final float overSize) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.overSize = overSize; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setCustomInternal(final String name, final String value) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + if (customFields == null) + customFields = new HashMap(); + if (value == null || "null".equalsIgnoreCase(value)) + customFields.remove(name); + else + customFields.put(name, value); + } finally { + releaseSchemaWriteLock(); + } + } + + private void clearCustomInternal() { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + customFields = null; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setNameInternal(final String name) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + final String oldName = this.name; + + owner.changeClassName(this.name, name, this); + this.name = name; + + ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (!database.getStorageVersions().classesAreDetectedByClusterId()) { + for (int clusterId : clusterIds) { + long[] range = storage.getClusterDataRange(clusterId); + + OPhysicalPosition[] positions = storage.ceilingPhysicalPositions(clusterId, new OPhysicalPosition(range[0])); + do { + for (OPhysicalPosition position : positions) { + final ORecordId identity = new ORecordId(clusterId, position.clusterPosition); + final ORawBuffer record = storage.readRecord(identity, null, true, false, null).getResult(); + + if (record.recordType == ODocument.RECORD_TYPE) { + final ORecordSerializerSchemaAware2CSV serializer = (ORecordSerializerSchemaAware2CSV) ORecordSerializerFactory + .instance().getFormat(ORecordSerializerSchemaAware2CSV.NAME); + String persName = new String(record.buffer, "UTF-8"); + if (serializer.getClassName(persName).equalsIgnoreCase(name)) { + final ODocument document = new ODocument(); + document.setLazyLoad(false); + document.fromStream(record.buffer); + ORecordInternal.setVersion(document, record.version); + ORecordInternal.setIdentity(document, identity); + document.setClassName(name); + document.setDirty(); + document.save(); + } + } + + if (positions.length > 0) + positions = storage.higherPhysicalPositions(clusterId, positions[positions.length - 1]); + } + } while (positions.length > 0); + } + } + + renameCluster(oldName, this.name); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSchemaException("Error reading schema"), e); + } finally { + releaseSchemaWriteLock(); + } + } + + private void renameCluster(String oldName, String newName) { + oldName = oldName.toLowerCase(Locale.ENGLISH); + newName = newName.toLowerCase(Locale.ENGLISH); + + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage.getClusterIdByName(newName) != -1) + return; + + final int clusterId = storage.getClusterIdByName(oldName); + if (clusterId == -1) + return; + + if (!hasClusterId(clusterId)) + return; + + database.command(new OCommandSQL("alter cluster `" + oldName + "` name `" + newName + "`")).execute(); + } + + private void setShortNameInternal(final String iShortName) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + String oldName = null; + + if (this.shortName != null) + oldName = this.shortName; + + owner.changeClassName(oldName, iShortName, this); + + this.shortName = iShortName; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setDescriptionInternal(final String iDescription) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + this.description = iDescription; + } finally { + releaseSchemaWriteLock(); + } + } + + private void dropPropertyInternal(final String iPropertyName) { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot drop a property inside a transaction"); + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + final OProperty prop = properties.remove(iPropertyName.toLowerCase(Locale.ENGLISH)); + + if (prop == null) + throw new OSchemaException("Property '" + iPropertyName + "' not found in class " + name + "'"); + } finally { + releaseSchemaWriteLock(); + } + } + + private OClass addClusterIdInternal(final int clusterId) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + owner.checkClusterCanBeAdded(clusterId, this); + + for (int currId : clusterIds) + if (currId == clusterId) + // ALREADY ADDED + return this; + + clusterIds = OArrays.copyOf(clusterIds, clusterIds.length + 1); + clusterIds[clusterIds.length - 1] = clusterId; + Arrays.sort(clusterIds); + + addPolymorphicClusterId(clusterId); + + if (defaultClusterId == NOT_EXISTENT_CLUSTER_ID) + defaultClusterId = clusterId; + + owner.addClusterForClass(clusterId, this); + return this; + } finally { + releaseSchemaWriteLock(); + } + } + + private void addPolymorphicClusterId(int clusterId) { + if (Arrays.binarySearch(polymorphicClusterIds, clusterId) >= 0) + return; + + polymorphicClusterIds = OArrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length + 1); + polymorphicClusterIds[polymorphicClusterIds.length - 1] = clusterId; + Arrays.sort(polymorphicClusterIds); + + addClusterIdToIndexes(clusterId); + + for (OClassImpl superClass : superClasses) { + superClass.addPolymorphicClusterId(clusterId); + } + } + + private OClass removeClusterIdInternal(final int clusterToRemove) { + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + boolean found = false; + for (int clusterId : clusterIds) { + if (clusterId == clusterToRemove) { + found = true; + break; + } + } + + if (found) { + final int[] newClusterIds = new int[clusterIds.length - 1]; + for (int i = 0, k = 0; i < clusterIds.length; ++i) { + if (clusterIds[i] == clusterToRemove) + // JUMP IT + continue; + + newClusterIds[k] = clusterIds[i]; + k++; + } + clusterIds = newClusterIds; + + removePolymorphicClusterId(clusterToRemove); + } + + if (defaultClusterId == clusterToRemove) { + if (clusterIds.length >= 1) + defaultClusterId = clusterIds[0]; + else + defaultClusterId = NOT_EXISTENT_CLUSTER_ID; + } + + owner.removeClusterForClass(clusterToRemove, this); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + private void setAbstractInternal(final boolean isAbstract) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + if (isAbstract) { + // SWITCH TO ABSTRACT + if (defaultClusterId != NOT_EXISTENT_CLUSTER_ID) { + // CHECK + if (count() > 0) + throw new IllegalStateException("Cannot set the class as abstract because contains records."); + + tryDropCluster(defaultClusterId); + for (int clusterId : getClusterIds()) { + tryDropCluster(clusterId); + removePolymorphicClusterId(clusterId); + owner.removeClusterForClass(clusterId, this); + } + + setClusterIds(new int[] { NOT_EXISTENT_CLUSTER_ID }); + + defaultClusterId = NOT_EXISTENT_CLUSTER_ID; + } + } else { + if (!abstractClass) + return; + + int clusterId = getDatabase().getClusterIdByName(name); + if (clusterId == -1) + clusterId = getDatabase().addCluster(name); + + this.defaultClusterId = clusterId; + this.clusterIds[0] = this.defaultClusterId; + this.polymorphicClusterIds = Arrays.copyOf(clusterIds, clusterIds.length); + for (OClass clazz : getAllSubclasses()) { + if (clazz instanceof OClassImpl) { + addPolymorphicClusterIds((OClassImpl) clazz); + } else { + OLogManager.instance().warn(this, "Warning: cannot set polymorphic cluster IDs for class " + name); + } + } + } + + this.abstractClass = isAbstract; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setStrictModeInternal(final boolean iStrict) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.strictMode = iStrict; + } finally { + releaseSchemaWriteLock(); + } + } + + private OProperty addProperty(final String propertyName, final OType type, final OType linkedType, final OClass linkedClass, + final boolean unsafe) { + if (type == null) + throw new OSchemaException("Property type not defined."); + + if (propertyName == null || propertyName.length() == 0) + throw new OSchemaException("Property name is null or empty"); + + if (getDatabase().getStorage().getConfiguration().isStrictSql()) { + validatePropertyName(propertyName); + } + if (getDatabase().getTransaction().isActive()) + throw new OSchemaException("Cannot create property '" + propertyName + "' inside a transaction"); + + final ODatabaseDocumentInternal database = getDatabase(); + database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + if (linkedType != null) + OPropertyImpl.checkLinkTypeSupport(type); + + if (linkedClass != null) + OPropertyImpl.checkSupportLinkedClass(type); + + acquireSchemaWriteLock(); + try { + final StringBuilder cmd = new StringBuilder("create property "); + // CLASS.PROPERTY NAME + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + cmd.append(name); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + cmd.append('.'); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + cmd.append(propertyName); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + + // TYPE + cmd.append(' '); + cmd.append(type.name); + + if (linkedType != null) { + // TYPE + cmd.append(' '); + cmd.append(linkedType.name); + + } else if (linkedClass != null) { + // TYPE + cmd.append(' '); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + cmd.append(linkedClass.getName()); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + } + + if (unsafe) + cmd.append(" unsafe "); + + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + database.command(new OCommandSQL(cmd.toString())).execute(); + reload(); + + return getProperty(propertyName); + } else if (isDistributedCommand()) { + final OProperty prop = (OProperty) OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public OProperty call() throws Exception { + return addPropertyInternal(propertyName, type, linkedType, linkedClass, unsafe); + } + }); + + final OCommandSQL commandSQL = new OCommandSQL(cmd.toString()); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + return prop; + } else + return (OProperty) OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public OProperty call() throws Exception { + return addPropertyInternal(propertyName, type, linkedType, linkedClass, unsafe); + } + }); + + } finally { + releaseSchemaWriteLock(); + } + } + + private void validatePropertyName(final String propertyName) { + } + + private int getClusterId(final String stringValue) { + int clId; + if (!stringValue.isEmpty() && Character.isDigit(stringValue.charAt(0))) + try { + clId = Integer.parseInt(stringValue); + } catch (NumberFormatException e) { + clId = getDatabase().getClusterIdByName(stringValue); + } + else + clId = getDatabase().getClusterIdByName(stringValue); + + return clId; + } + + private void addClusterIdToIndexes(int iId) { + if (getDatabase().getStorage().getUnderlying() instanceof OAbstractPaginatedStorage) { + final String clusterName = getDatabase().getClusterNameById(iId); + final List indexesToAdd = new ArrayList(); + + for (OIndex index : getIndexes()) + indexesToAdd.add(index.getName()); + + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + for (String indexName : indexesToAdd) + indexManager.addClusterToIndex(clusterName, indexName); + } + } + + /** + * Adds a base class to the current one. It adds also the base class cluster ids to the polymorphic cluster ids array. + * + * @param iBaseClass The base class to add. + */ + private OClass addBaseClass(final OClassImpl iBaseClass) { + checkRecursion(iBaseClass); + + if (subclasses == null) + subclasses = new ArrayList(); + + if (subclasses.contains(iBaseClass)) + return this; + + subclasses.add(iBaseClass); + addPolymorphicClusterIdsWithInheritance(iBaseClass); + return this; + } + + private void checkParametersConflict(final OClass baseClass) { + final Collection baseClassProperties = baseClass.properties(); + for (OProperty property : baseClassProperties) { + OProperty thisProperty = getProperty(property.getName()); + if (thisProperty != null && !thisProperty.getType().equals(property.getType())) { + throw new OSchemaException( + "Cannot add base class '" + baseClass.getName() + "', because of property conflict: '" + thisProperty + "' vs '" + + property + "'"); + } + } + } + + protected static void checkParametersConflict(List classes) { + final Map comulative = new HashMap(); + final Map properties = new HashMap(); + + for (OClass superClass : classes) { + if (superClass == null) + continue; + OClassImpl impl; + + if (superClass instanceof OClassAbstractDelegate) + impl = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate; + else + impl = (OClassImpl) superClass; + impl.propertiesMap(properties, false); + for (Map.Entry entry : properties.entrySet()) { + if (comulative.containsKey(entry.getKey())) { + final String property = entry.getKey(); + final OProperty existingProperty = comulative.get(property); + if (!existingProperty.getType().equals(entry.getValue().getType())) { + throw new OSchemaException("Properties conflict detected: '" + existingProperty + "] vs [" + entry.getValue() + "]"); + } + } + } + + comulative.putAll(properties); + properties.clear(); + } + } + + private void checkRecursion(final OClass baseClass) { + if (isSubClassOf(baseClass)) { + throw new OSchemaException("Cannot add base class '" + baseClass.getName() + "', because of recursion"); + } + } + + private void removePolymorphicClusterIds(final OClassImpl iBaseClass) { + for (final int clusterId : iBaseClass.polymorphicClusterIds) + removePolymorphicClusterId(clusterId); + } + + private void removePolymorphicClusterId(final int clusterId) { + final int index = Arrays.binarySearch(polymorphicClusterIds, clusterId); + if (index < 0) + return; + + if (index < polymorphicClusterIds.length - 1) + System.arraycopy(polymorphicClusterIds, index + 1, polymorphicClusterIds, index, polymorphicClusterIds.length - (index + 1)); + + polymorphicClusterIds = Arrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length - 1); + + removeClusterFromIndexes(clusterId); + for (OClassImpl superClass : superClasses) { + superClass.removePolymorphicClusterId(clusterId); + } + } + + private void removeClusterFromIndexes(final int iId) { + if (getDatabase().getStorage().getUnderlying() instanceof OAbstractPaginatedStorage) { + final String clusterName = getDatabase().getClusterNameById(iId); + final List indexesToRemove = new ArrayList(); + + for (final OIndex index : getIndexes()) + indexesToRemove.add(index.getName()); + + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + for (final String indexName : indexesToRemove) + indexManager.removeClusterFromIndex(clusterName, indexName); + } + } + + private void tryDropCluster(final int defaultClusterId) { + if (name.toLowerCase(Locale.ENGLISH).equals(getDatabase().getClusterNameById(defaultClusterId))) { + // DROP THE DEFAULT CLUSTER CALLED WITH THE SAME NAME ONLY IF EMPTY + if (getDatabase().getClusterRecordSizeById(defaultClusterId) == 0) + getDatabase().getStorage().dropCluster(defaultClusterId, true); + } + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + /** + * Add different cluster id to the "polymorphic cluster ids" array. + */ + private void addPolymorphicClusterIds(final OClassImpl iBaseClass) { + Set clusters = new TreeSet(); + + for (int clusterId : polymorphicClusterIds) { + clusters.add(clusterId); + } + for (int clusterId : iBaseClass.polymorphicClusterIds) { + if (clusters.add(clusterId)) { + try { + addClusterIdToIndexes(clusterId); + } catch (RuntimeException e) { + OLogManager.instance().warn(this, "Error adding clusterId '%d' to index of class '%s'", e, clusterId, getName()); + clusters.remove(clusterId); + } + } + } + polymorphicClusterIds = new int[clusters.size()]; + int i = 0; + for (Integer cluster : clusters) { + polymorphicClusterIds[i] = cluster; + i++; + } + } + + private void addPolymorphicClusterIdsWithInheritance(final OClassImpl iBaseClass) { + addPolymorphicClusterIds(iBaseClass); + for (OClassImpl superClass : superClasses) { + superClass.addPolymorphicClusterIdsWithInheritance(iBaseClass); + } + } + + public List extractFieldTypes(final String[] fieldNames) { + final List types = new ArrayList(fieldNames.length); + + for (String fieldName : fieldNames) { + if (!fieldName.equals("@rid")) + types.add(getProperty(decodeClassName(OIndexDefinitionFactory.extractFieldName(fieldName)).toLowerCase(Locale.ENGLISH)).getType()); + else + types.add(OType.LINK); + } + return types; + } + + private OClass setClusterIds(final int[] iClusterIds) { + clusterIds = iClusterIds; + Arrays.sort(clusterIds); + + return this; + } + + private boolean isDistributedCommand() { + return getDatabase().getStorage() instanceof OAutoshardedStorage && !OScenarioThreadLocal.INSTANCE.isRunModeDistributed(); + } + + public static String decodeClassName(String s) { + if (s == null) { + return null; + } + s = s.trim(); + if (s.startsWith("`") && s.endsWith("`")) { + return s.substring(1, s.length() - 1); + } + return s; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalProperty.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalProperty.java new file mode 100644 index 00000000000..a454304a477 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalProperty.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.metadata.schema; + +public interface OGlobalProperty { + + Integer getId(); + + String getName(); + + OType getType(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalPropertyImpl.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalPropertyImpl.java new file mode 100644 index 00000000000..9157b05535c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OGlobalPropertyImpl.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; + +public class OGlobalPropertyImpl implements OGlobalProperty, ODocumentSerializable { + + private String name; + private OType type; + private Integer id; + + public OGlobalPropertyImpl() { + } + + public OGlobalPropertyImpl(final String name, final OType type, final Integer id) { + this.name = name; + this.type = type; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public OType getType() { + return type; + } + + @Override + public void fromDocument(final ODocument document) { + this.name = document.field("name"); + this.type = OType.valueOf((String) document.field("type")); + this.id = document.field("id"); + } + + @Override + public ODocument toDocument() { + final ODocument doc = new ODocument(); + doc.field("name", name); + doc.field("type", type.name()); + doc.field("id", id); + return doc; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableClass.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableClass.java new file mode 100644 index 00000000000..609852d4fad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableClass.java @@ -0,0 +1,823 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OClassTrigger; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexManager; +import com.orientechnologies.orient.core.metadata.function.OFunctionTrigger; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.schedule.OScheduledEvent; + +import java.io.IOException; +import java.util.*; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 10/21/14 + */ +public class OImmutableClass implements OClass { + /** + * use OClass.EDGE_CLASS_NAME instead + */ + @Deprecated + public static final String EDGE_CLASS_NAME = OClass.EDGE_CLASS_NAME; + /** + * use OClass.EDGE_CLASS_NAME instead + */ + @Deprecated + public static final String VERTEX_CLASS_NAME = OClass.VERTEX_CLASS_NAME; + + + private boolean inited = false; + private final boolean isAbstract; + private final boolean strictMode; + private final String name; + private final String streamAbleName; + private final Map properties; + private Map allPropertiesMap; + private Collection allProperties; + private final OClusterSelectionStrategy clusterSelection; + private final int defaultClusterId; + private final int[] clusterIds; + private final int[] polymorphicClusterIds; + private final Collection baseClassesNames; + private final List superClassesNames; + private final float overSize; + private final float classOverSize; + private final String shortName; + private final Map customFields; + private final String description; + + private final OImmutableSchema schema; + // do not do it volatile it is already SAFE TO USE IT in MT mode. + private final List superClasses; + // do not do it volatile it is already SAFE TO USE IT in MT mode. + private Collection subclasses; + private boolean restricted; + private boolean isVertexType; + private boolean isEdgeType; + private boolean triggered; + private boolean function; + private boolean scheduler; + private boolean ouser; + private boolean orole; + private OIndex autoShardingIndex; + + public OImmutableClass(final OClass oClass, final OImmutableSchema schema) { + isAbstract = oClass.isAbstract(); + strictMode = oClass.isStrictMode(); + this.schema = schema; + + superClassesNames = oClass.getSuperClassesNames(); + superClasses = new ArrayList(superClassesNames.size()); + + name = oClass.getName(); + streamAbleName = oClass.getStreamableName(); + clusterSelection = oClass.getClusterSelection(); + defaultClusterId = oClass.getDefaultClusterId(); + clusterIds = oClass.getClusterIds(); + polymorphicClusterIds = oClass.getPolymorphicClusterIds(); + + baseClassesNames = new ArrayList(); + for (OClass baseClass : oClass.getSubclasses()) + baseClassesNames.add(baseClass.getName()); + + overSize = oClass.getOverSize(); + classOverSize = oClass.getClassOverSize(); + shortName = oClass.getShortName(); + + properties = new HashMap(); + for (OProperty p : oClass.declaredProperties()) + properties.put(p.getName().toLowerCase(Locale.ENGLISH), new OImmutableProperty(p, this)); + + Map customFields = new HashMap(); + for (String key : oClass.getCustomKeys()) + customFields.put(key, oClass.getCustom(key)); + + this.customFields = Collections.unmodifiableMap(customFields); + this.description = oClass.getDescription(); + } + + public void init() { + if (!inited) { + initSuperClasses(); + + final Collection allProperties = new ArrayList(); + final Map allPropsMap = new HashMap(20); + for (int i = superClasses.size() - 1; i >= 0; i--) { + allProperties.addAll(superClasses.get(i).allProperties); + allPropsMap.putAll(superClasses.get(i).allPropertiesMap); + } + allProperties.addAll(properties.values()); + for (OProperty p : properties.values()) { + final String propName = p.getName(); + + if (!allPropsMap.containsKey(propName)) + allPropsMap.put(propName, p); + } + + this.allProperties = Collections.unmodifiableCollection(allProperties); + this.allPropertiesMap = Collections.unmodifiableMap(allPropsMap); + this.restricted = isSubClassOf(OSecurityShared.RESTRICTED_CLASSNAME); + this.isVertexType = isSubClassOf(OClass.VERTEX_CLASS_NAME); + this.isEdgeType = isSubClassOf(OClass.EDGE_CLASS_NAME); + this.triggered = isSubClassOf(OClassTrigger.CLASSNAME); + this.function = isSubClassOf(OFunctionTrigger.CLASSNAME); + this.scheduler = isSubClassOf(OScheduledEvent.CLASS_NAME); + this.ouser = isSubClassOf(OUser.CLASS_NAME); + this.orole = isSubClassOf(ORole.CLASS_NAME); + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + this.autoShardingIndex = db != null && db.getMetadata() != null && db.getMetadata().getIndexManager() != null + ? db.getMetadata().getIndexManager().getClassAutoShardingIndex(name) : null; + } + + inited = true; + } + + @Override + public boolean isAbstract() { + return isAbstract; + } + + @Override + public OClass setAbstract(boolean iAbstract) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isStrictMode() { + return strictMode; + } + + @Override + public OClass setStrictMode(boolean iMode) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass getSuperClass() { + initSuperClasses(); + + return superClasses.isEmpty() ? null : superClasses.get(0); + } + + @Override + public OClass setSuperClass(OClass iSuperClass) { + throw new UnsupportedOperationException(); + } + + @Override + public List getSuperClasses() { + return Collections.unmodifiableList((List) superClasses); + } + + @Override + public boolean hasSuperClasses() { + return !superClasses.isEmpty(); + } + + @Override + public List getSuperClassesNames() { + return superClassesNames; + } + + @Override + public OClass setSuperClasses(List classes) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass addSuperClass(OClass superClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass removeSuperClass(OClass superClass) { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return name; + } + + @Override + public OClass setName(String iName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getStreamableName() { + return streamAbleName; + } + + @Override + public Collection declaredProperties() { + return Collections.unmodifiableCollection(properties.values()); + } + + @Override + public Collection properties() { + return allProperties; + } + + @Override + public Map propertiesMap() { + return allPropertiesMap; + } + + public void getIndexedProperties(Collection indexedProperties) { + for (OProperty p : properties.values()) + if (areIndexed(p.getName())) + indexedProperties.add(p); + initSuperClasses(); + for (OImmutableClass superClass : superClasses) { + superClass.getIndexedProperties(indexedProperties); + } + } + + @Override + public Collection getIndexedProperties() { + Collection indexedProps = new HashSet(); + getIndexedProperties(indexedProps); + return indexedProps; + } + + @Override + public OProperty getProperty(String propertyName) { + initSuperClasses(); + + propertyName = propertyName.toLowerCase(Locale.ENGLISH); + + OProperty p = properties.get(propertyName); + if (p != null) + return p; + for (int i = 0; i < superClasses.size() && p == null; i++) { + p = superClasses.get(i).getProperty(propertyName); + } + return p; + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OClass iLinkedClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OClass iLinkedClass, boolean unsafe) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OType iLinkedType) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty createProperty(String iPropertyName, OType iType, OType iLinkedType, boolean unsafe) { + throw new UnsupportedOperationException(); + } + + @Override + public void dropProperty(String iPropertyName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean existsProperty(String propertyName) { + propertyName = propertyName.toLowerCase(Locale.ENGLISH); + boolean result = properties.containsKey(propertyName); + if (result) + return true; + for (OImmutableClass superClass : superClasses) { + result = superClass.existsProperty(propertyName); + if (result) + return true; + } + return false; + } + + @Override + public int getClusterForNewInstance(final ODocument doc) { + return clusterSelection.getCluster(this, doc); + } + + @Override + public int getDefaultClusterId() { + return defaultClusterId; + } + + @Override + public void setDefaultClusterId(int iDefaultClusterId) { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getClusterIds() { + return clusterIds; + } + + @Override + public OClass addClusterId(int iId) { + throw new UnsupportedOperationException(); + } + + @Override + public OClusterSelectionStrategy getClusterSelection() { + return clusterSelection; + } + + @Override + public OClass setClusterSelection(OClusterSelectionStrategy clusterSelection) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass setClusterSelection(String iStrategyName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass addCluster(String iClusterName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass truncateCluster(String clusterName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass removeClusterId(int iId) { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getPolymorphicClusterIds() { + return Arrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length); + } + + public OImmutableSchema getSchema() { + return schema; + } + + @Override + public Collection getSubclasses() { + initBaseClasses(); + + ArrayList result = new ArrayList(); + for (OClass c : subclasses) + result.add(c); + + return result; + } + + @Override + public Collection getAllSubclasses() { + initBaseClasses(); + + final Set set = new HashSet(); + set.addAll(getSubclasses()); + + for (OImmutableClass c : subclasses) + set.addAll(c.getAllSubclasses()); + + return set; + } + + @Override + public Collection getBaseClasses() { + return getSubclasses(); + } + + @Override + public Collection getAllBaseClasses() { + return getAllSubclasses(); + } + + @Override + public Collection getAllSuperClasses() { + Set ret = new HashSet(); + getAllSuperClasses(ret); + return ret; + } + + private void getAllSuperClasses(Set set) { + set.addAll(superClasses); + for (OImmutableClass superClass : superClasses) { + superClass.getAllSuperClasses(set); + } + } + + @Override + public long getSize() { + long size = 0; + for (int clusterId : clusterIds) + size += getDatabase().getClusterRecordSizeById(clusterId); + + return size; + } + + @Override + public float getOverSize() { + return overSize; + } + + @Override + public float getClassOverSize() { + return classOverSize; + } + + @Override + public OClass setOverSize(float overSize) { + throw new UnsupportedOperationException(); + } + + @Override + public long count() { + return count(true); + } + + @Override + public long count(boolean isPolymorphic) { + if (isPolymorphic) + return getDatabase().countClusterElements(OClassImpl.readableClusters(getDatabase(), polymorphicClusterIds)); + + return getDatabase().countClusterElements(OClassImpl.readableClusters(getDatabase(), clusterIds)); + } + + @Override + public void truncate() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSubClassOf(final String iClassName) { + if (iClassName == null) + return false; + + if (iClassName.equalsIgnoreCase(getName()) || iClassName.equalsIgnoreCase(getShortName())) + return true; + + final int s = superClasses.size(); + for (int i = 0; i < s; ++i) { + if (superClasses.get(i).isSubClassOf(iClassName)) + return true; + } + + return false; + } + + @Override + public boolean isSubClassOf(final OClass clazz) { + if (clazz == null) + return false; + if (equals(clazz)) + return true; + + final int s = superClasses.size(); + for (int i = 0; i < s; ++i) { + if (superClasses.get(i).isSubClassOf(clazz)) + return true; + } + return false; + } + + @Override + public boolean isSuperClassOf(OClass clazz) { + return clazz != null && clazz.isSubClassOf(this); + } + + @Override + public String getShortName() { + return shortName; + } + + @Override + public OClass setShortName(String shortName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDescription() { + return description; + } + + @Override + public OClass setDescription(String iDescription) { + throw new UnsupportedOperationException(); + } + + @Override + public Object get(ATTRIBUTES iAttribute) { + if (iAttribute == null) + throw new IllegalArgumentException("attribute is null"); + + switch (iAttribute) { + case NAME: + return getName(); + case SHORTNAME: + return getShortName(); + case SUPERCLASS: + return getSuperClass(); + case SUPERCLASSES: + return getSuperClasses(); + case OVERSIZE: + return getOverSize(); + case STRICTMODE: + return isStrictMode(); + case ABSTRACT: + return isAbstract(); + case CLUSTERSELECTION: + return getClusterSelection(); + case CUSTOM: + return getCustomInternal(); + case DESCRIPTION: + return getDescription(); + } + + throw new IllegalArgumentException("Cannot find attribute '" + iAttribute + "'"); + } + + @Override + public OClass set(ATTRIBUTES attribute, Object iValue) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iName, INDEX_TYPE iType, String... fields) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iName, String iType, String... fields) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iName, INDEX_TYPE iType, OProgressListener iProgressListener, String... fields) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata, + String algorithm, String... fields) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata, + String... fields) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> getInvolvedIndexes(Collection fields) { + initSuperClasses(); + + final Set> result = new HashSet>(getClassInvolvedIndexes(fields)); + + for (OImmutableClass superClass : superClasses) { + result.addAll(superClass.getInvolvedIndexes(fields)); + } + return result; + } + + @Override + public Set> getInvolvedIndexes(String... fields) { + return getInvolvedIndexes(Arrays.asList(fields)); + } + + @Override + public Set> getClassInvolvedIndexes(Collection fields) { + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + return indexManager.getClassInvolvedIndexes(name, fields); + } + + @Override + public Set> getClassInvolvedIndexes(String... fields) { + return getClassInvolvedIndexes(Arrays.asList(fields)); + } + + @Override + public boolean areIndexed(Collection fields) { + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + final boolean currentClassResult = indexManager.areIndexed(name, fields); + + initSuperClasses(); + + if (currentClassResult) + return true; + for (OImmutableClass superClass : superClasses) { + if (superClass.areIndexed(fields)) + return true; + } + return false; + + } + + @Override + public boolean areIndexed(String... fields) { + return areIndexed(Arrays.asList(fields)); + } + + @Override + public OIndex getClassIndex(String iName) { + return getDatabase().getMetadata().getIndexManager().getClassIndex(this.name, iName); + } + + @Override + public Set> getClassIndexes() { + return getDatabase().getMetadata().getIndexManager().getClassIndexes(name); + } + + @Override + public void getClassIndexes(final Collection> indexes) { + getDatabase().getMetadata().getIndexManager().getClassIndexes(name, indexes); + } + + @Override + public void getIndexes(final Collection> indexes) { + initSuperClasses(); + + getClassIndexes(indexes); + for (OClass superClass : superClasses) { + superClass.getIndexes(indexes); + } + } + + @Override + public Set> getIndexes() { + final Set> indexes = new HashSet>(); + getIndexes(indexes); + return indexes; + } + + @Override + public OIndex getAutoShardingIndex() { + return autoShardingIndex; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!OClass.class.isAssignableFrom(obj.getClass())) + return false; + final OClass other = (OClass) obj; + if (name == null) { + if (other.getName() != null) + return false; + } else if (!name.equals(other.getName())) + return false; + return true; + } + + @Override + public String toString() { + return name; + } + + @Override + public String getCustom(final String iName) { + return customFields.get(iName); + } + + @Override + public OClass setCustom(String iName, String iValue) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeCustom(String iName) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearCustom() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getCustomKeys() { + return Collections.unmodifiableSet(customFields.keySet()); + } + + @Override + public boolean hasClusterId(final int clusterId) { + return Arrays.binarySearch(clusterIds, clusterId) >= 0; + } + + @Override + public boolean hasPolymorphicClusterId(final int clusterId) { + return Arrays.binarySearch(polymorphicClusterIds, clusterId) >= 0; + } + + @Override + public int compareTo(final OClass other) { + return name.compareTo(other.getName()); + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + private Map getCustomInternal() { + return customFields; + } + + private void initSuperClasses() { + if (superClassesNames != null && superClassesNames.size() != superClasses.size()) { + superClasses.clear(); + for (String superClassName : superClassesNames) { + OImmutableClass superClass = (OImmutableClass) schema.getClass(superClassName); + superClass.init(); + superClasses.add(superClass); + } + } + } + + private void initBaseClasses() { + if (subclasses == null) { + final List result = new ArrayList(baseClassesNames.size()); + for (String clsName : baseClassesNames) + result.add((OImmutableClass) schema.getClass(clsName)); + + subclasses = result; + } + } + + public boolean isRestricted() { + return restricted; + } + + public boolean isEdgeType() { + return isEdgeType; + } + + public boolean isVertexType() { + return isVertexType; + } + + public boolean isTriggered() { + return triggered; + } + + public boolean isFunction() { + return function; + } + + public boolean isScheduler() { + return scheduler; + } + + public boolean isOuser() { + return ouser; + } + + public boolean isOrole() { + return orole; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableProperty.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableProperty.java new file mode 100755 index 00000000000..7c0db255d65 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableProperty.java @@ -0,0 +1,470 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.metadata.schema.validation.ValidationBinaryComparable; +import com.orientechnologies.orient.core.metadata.schema.validation.ValidationCollectionComparable; +import com.orientechnologies.orient.core.metadata.schema.validation.ValidationMapComparable; +import com.orientechnologies.orient.core.metadata.schema.validation.ValidationStringComparable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 10/21/14 + */ +public class OImmutableProperty implements OProperty { + private final String name; + private final String fullName; + private final OType type; + private final String description; + + // do not make it volatile it is already thread safe. + private OClass linkedClass = null; + + private final String linkedClassName; + + private final OType linkedType; + private final boolean notNull; + private final OCollate collate; + private final boolean mandatory; + private final String min; + private final String max; + private final String defaultValue; + private final String regexp; + private final Map customProperties; + private final OClass owner; + private final Integer id; + private final boolean readOnly; + private final Comparable minComparable; + private final Comparable maxComparable; + + public OImmutableProperty(OProperty property, OImmutableClass owner) { + name = property.getName(); + fullName = property.getFullName(); + type = property.getType(); + description = property.getDescription(); + + if (property.getLinkedClass() != null) + linkedClassName = property.getLinkedClass().getName(); + else + linkedClassName = null; + + linkedType = property.getLinkedType(); + notNull = property.isNotNull(); + collate = property.getCollate(); + mandatory = property.isMandatory(); + min = property.getMin(); + max = property.getMax(); + defaultValue = property.getDefaultValue(); + regexp = property.getRegexp(); + customProperties = new HashMap(); + + for (String key : property.getCustomKeys()) + customProperties.put(key, property.getCustom(key)); + + this.owner = owner; + id = property.getId(); + readOnly = property.isReadonly(); + + if (min != null) { + if (type.equals(OType.STRING)) + minComparable = new ValidationStringComparable((Integer) OType.convert(min, Integer.class)); + else if (type.equals(OType.BINARY)) + minComparable = new ValidationBinaryComparable((Integer) OType.convert(min, Integer.class)); + else if (type.equals(OType.DATE) || type.equals(OType.BYTE) || type.equals(OType.SHORT) || type.equals(OType.INTEGER) + || type.equals(OType.LONG) || type.equals(OType.FLOAT) || type.equals(OType.DOUBLE) || type.equals(OType.DECIMAL) + || type.equals(OType.DATETIME)) + minComparable = (Comparable) OType.convert(min, type.getDefaultJavaType()); + else if (type.equals(OType.EMBEDDEDLIST) || type.equals(OType.EMBEDDEDSET) || type.equals(OType.LINKLIST) + || type.equals(OType.LINKSET)) + minComparable = new ValidationCollectionComparable((Integer) OType.convert(min, Integer.class)); + else if (type.equals(OType.EMBEDDEDMAP) || type.equals(OType.LINKMAP)) + minComparable = new ValidationMapComparable((Integer) OType.convert(min, Integer.class)); + else + minComparable = null; + } else + minComparable = null; + + if (max != null) { + if (type.equals(OType.STRING)) + maxComparable = new ValidationStringComparable((Integer) OType.convert(max, Integer.class)); + else if (type.equals(OType.BINARY)) + maxComparable = new ValidationBinaryComparable((Integer) OType.convert(max, Integer.class)); + else if (type.equals(OType.DATE)) { + // This is needed because a date is valid in any time range of the day. + Date maxDate = (Date) OType.convert(max, OType.DATE.getDefaultJavaType()); + Calendar cal = Calendar.getInstance(); + cal.setTime(maxDate); + cal.add(Calendar.DAY_OF_MONTH, 1); + maxDate = new Date(cal.getTime().getTime() - 1); + maxComparable = (Comparable) maxDate; + } else if (type.equals(OType.BYTE) || type.equals(OType.SHORT) || type.equals(OType.INTEGER) || type.equals(OType.LONG) + || type.equals(OType.FLOAT) || type.equals(OType.DOUBLE) || type.equals(OType.DECIMAL) || type.equals(OType.DATETIME)) + maxComparable = (Comparable) OType.convert(max, type.getDefaultJavaType()); + else if (type.equals(OType.EMBEDDEDLIST) || type.equals(OType.EMBEDDEDSET) || type.equals(OType.LINKLIST) + || type.equals(OType.LINKSET)) + maxComparable = new ValidationCollectionComparable((Integer) OType.convert(max, Integer.class)); + else if (type.equals(OType.EMBEDDEDMAP) || type.equals(OType.LINKMAP)) + maxComparable = new ValidationMapComparable((Integer) OType.convert(max, Integer.class)); + else + maxComparable = null; + } else { + maxComparable = null; + } + } + + @Override + public String getName() { + return name; + } + + @Override + public String getFullName() { + return fullName; + } + + + @Override + public OProperty setName(String iName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDescription() { + return description; + } + + @Override + public OProperty setDescription(String iDescription) { + throw new UnsupportedOperationException(); + } + + @Override + public void set(ATTRIBUTES attribute, Object iValue) { + throw new UnsupportedOperationException(); + } + + @Override + public OType getType() { + return type; + } + + @Override + public OClass getLinkedClass() { + if (linkedClassName == null) + return null; + + if (linkedClass != null) + return linkedClass; + + OSchema schema = ((OImmutableClass) owner).getSchema(); + linkedClass = schema.getClass(linkedClassName); + + return linkedClass; + } + + @Override + public OProperty setLinkedClass(OClass oClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OType getLinkedType() { + return linkedType; + } + + @Override + public OProperty setLinkedType(OType type) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNotNull() { + return notNull; + } + + @Override + public OProperty setNotNull(boolean iNotNull) { + throw new UnsupportedOperationException(); + } + + @Override + public OCollate getCollate() { + return collate; + } + + @Override + public OProperty setCollate(String iCollateName) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty setCollate(OCollate collate) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isMandatory() { + return mandatory; + } + + @Override + public OProperty setMandatory(boolean mandatory) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReadonly() { + return readOnly; + } + + @Override + public OProperty setReadonly(boolean iReadonly) { + throw new UnsupportedOperationException(); + } + + @Override + public String getMin() { + return min; + } + + @Override + public OProperty setMin(String min) { + throw new UnsupportedOperationException(); + } + + @Override + public String getMax() { + return max; + } + + @Override + public OProperty setMax(String max) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultValue() { + return defaultValue; + } + + @Override + public OProperty setDefaultValue(String defaultValue) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(OClass.INDEX_TYPE iType) { + throw new UnsupportedOperationException(); + } + + @Override + public OIndex createIndex(String iType) { + throw new UnsupportedOperationException(); + } + + @Override public OIndex createIndex(String iType, ODocument metadata) { + throw new UnsupportedOperationException(); + } + + @Override public OIndex createIndex(OClass.INDEX_TYPE iType, ODocument metadata) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty dropIndexes() { + throw new UnsupportedOperationException(); + } + + @Override + public Set> getIndexes() { + return owner.getInvolvedIndexes(name); + } + + @Override + public OIndex getIndex() { + Set> indexes = owner.getInvolvedIndexes(name); + if (indexes != null && !indexes.isEmpty()) + return indexes.iterator().next(); + return null; + + } + + @Override + public Collection> getAllIndexes() { + final Set> indexes = owner.getIndexes(); + final List> indexList = new LinkedList>(); + for (final OIndex index : indexes) { + final OIndexDefinition indexDefinition = index.getDefinition(); + if (indexDefinition.getFields().contains(name)) + indexList.add(index); + } + + return indexList; + } + + @Override + public boolean isIndexed() { + return owner.areIndexed(name); + } + + @Override + public String getRegexp() { + return regexp; + } + + @Override + public OProperty setRegexp(String regexp) { + throw new UnsupportedOperationException(); + } + + @Override + public OProperty setType(OType iType) { + throw new UnsupportedOperationException(); + } + + @Override + public String getCustom(String iName) { + return customProperties.get(iName); + } + + @Override + public OProperty setCustom(String iName, String iValue) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeCustom(String iName) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearCustom() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getCustomKeys() { + return Collections.unmodifiableSet(customProperties.keySet()); + } + + @Override + public OClass getOwnerClass() { + return owner; + } + + @Override + public Object get(ATTRIBUTES attribute) { + if (attribute == null) + throw new IllegalArgumentException("attribute is null"); + + switch (attribute) { + case LINKEDCLASS: + return getLinkedClass(); + case LINKEDTYPE: + return getLinkedType(); + case MIN: + return getMin(); + case MANDATORY: + return isMandatory(); + case READONLY: + return isReadonly(); + case MAX: + return getMax(); + case DEFAULT: + return getDefaultValue(); + case NAME: + return getName(); + case NOTNULL: + return isNotNull(); + case REGEXP: + return getRegexp(); + case TYPE: + return getType(); + case COLLATE: + return getCollate(); + case DESCRIPTION: + return getDescription(); + } + + throw new IllegalArgumentException("Cannot find attribute '" + attribute + "'"); + } + + @Override + public Integer getId() { + return id; + } + + @Override + public int compareTo(OProperty other) { + return name.compareTo(other.getName()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((owner == null) ? 0 : owner.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!OProperty.class.isAssignableFrom(obj.getClass())) + return false; + OProperty other = (OProperty) obj; + if (owner == null) { + if (other.getOwnerClass() != null) + return false; + } else if (!owner.equals(other.getOwnerClass())) + return false; + return true; + } + + @Override + public String toString() { + return getName() + " (type=" + getType() + ")"; + } + + public Comparable getMaxComparable() { + return maxComparable; + } + + public Comparable getMinComparable() { + return minComparable; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableSchema.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableSchema.java new file mode 100755 index 00000000000..d35af838a9b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OImmutableSchema.java @@ -0,0 +1,287 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import com.orientechnologies.common.util.OArrays; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionFactory; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 10/21/14 + */ +public class OImmutableSchema implements OSchema { + private final Map clustersToClasses; + private final Map classes; + private final Set blogClusters; + + public final int version; + private final ORID identity; + private final boolean clustersCanNotBeSharedAmongClasses; + private final List properties; + private final OClusterSelectionFactory clusterSelectionFactory; + + public OImmutableSchema(OSchemaShared schemaShared) { + version = schemaShared.getVersion(); + identity = schemaShared.getIdentity(); + clustersCanNotBeSharedAmongClasses = schemaShared.isClustersCanNotBeSharedAmongClasses(); + clusterSelectionFactory = schemaShared.getClusterSelectionFactory(); + + clustersToClasses = new HashMap(schemaShared.getClasses().size() * 3); + classes = new HashMap(schemaShared.getClasses().size()); + + for (OClass oClass : schemaShared.getClasses()) { + final OImmutableClass immutableClass = new OImmutableClass(oClass, this); + + classes.put(immutableClass.getName().toLowerCase(Locale.ENGLISH), immutableClass); + if (immutableClass.getShortName() != null) + classes.put(immutableClass.getShortName().toLowerCase(Locale.ENGLISH), immutableClass); + + for (int clusterId : immutableClass.getClusterIds()) + clustersToClasses.put(clusterId, immutableClass); + } + + properties = new ArrayList(); + for (OGlobalProperty globalProperty : schemaShared.getGlobalProperties()) + properties.add(globalProperty); + + for (OClass cl : classes.values()) { + ((OImmutableClass) cl).init(); + } + this.blogClusters = Collections.unmodifiableSet(new HashSet(schemaShared.getBlobClusters())); + } + + @Override + public OImmutableSchema makeSnapshot() { + return this; + } + + @Override + public int countClasses() { + return classes.size(); + } + + @Override + public OClass createClass(Class iClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String iClassName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String iClassName, OClass iSuperClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String iClassName, OClass... superClasses) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String iClassName, OClass iSuperClass, int[] iClusterIds) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String className, int clusters, OClass... superClasses) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createClass(String className, int[] clusterIds, OClass... superClasses) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createAbstractClass(Class iClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createAbstractClass(String iClassName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createAbstractClass(String iClassName, OClass iSuperClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass createAbstractClass(String iClassName, OClass... superClasses) { + throw new UnsupportedOperationException(); + } + + @Override + public void dropClass(String iClassName) { + throw new UnsupportedOperationException(); + } + + @Override + public RET reload() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean existsClass(String iClassName) { + return classes.containsKey(iClassName.toLowerCase(Locale.ENGLISH)); + } + + @Override + public OClass getClass(Class iClass) { + if (iClass == null) + return null; + + return getClass(iClass.getSimpleName()); + } + + @Override + public OClass getClass(String iClassName) { + if (iClassName == null) + return null; + + OClass cls = classes.get(iClassName.toLowerCase(Locale.ENGLISH)); + if (cls != null) + return cls; + + return null; + } + + @Override + public OClass getOrCreateClass(String iClassName) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass getOrCreateClass(String iClassName, OClass iSuperClass) { + throw new UnsupportedOperationException(); + } + + @Override + public OClass getOrCreateClass(String iClassName, OClass... superClasses) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getClasses() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + return new HashSet(classes.values()); + } + + @Override + public void create() { + throw new UnsupportedOperationException(); + } + + @Override + public int getVersion() { + return version; + } + + @Override + public ORID getIdentity() { + return new ORecordId(identity); + } + + @Override + public RET save() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getClassesRelyOnCluster(String clusterName) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + final int clusterId = getDatabase().getClusterIdByName(clusterName); + final Set result = new HashSet(); + for (OClass c : classes.values()) { + if (OArrays.contains(c.getPolymorphicClusterIds(), clusterId)) + result.add(c); + } + + return result; + } + + @Override + public OClass getClassByClusterId(int clusterId) { + if (!clustersCanNotBeSharedAmongClasses) + throw new OSchemaException("This feature is not supported in current version of binary format."); + + return clustersToClasses.get(clusterId); + + } + + @Override + public OGlobalProperty getGlobalPropertyById(int id) { + if (id >= properties.size()) + return null; + return properties.get(id); + } + + @Override + public List getGlobalProperties() { + return Collections.unmodifiableList(properties); + } + + @Override + public OGlobalProperty createGlobalProperty(String name, OType type, Integer id) { + throw new UnsupportedOperationException(); + } + + @Override + public OClusterSelectionFactory getClusterSelectionFactory() { + return clusterSelectionFactory; + } + + @Override + public void onPostIndexManagement() { + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public Set getBlobClusters() { + return blogClusters; + } + + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OProperty.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OProperty.java new file mode 100755 index 00000000000..324e37c7c90 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OProperty.java @@ -0,0 +1,276 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Set; + +/** + * Contains the description of a persistent class property. + * + * @author Luca Garulli + * + */ +public interface OProperty extends Comparable { + + public static enum ATTRIBUTES { + LINKEDTYPE, LINKEDCLASS, MIN, MAX, MANDATORY, NAME, NOTNULL, REGEXP, TYPE, CUSTOM, READONLY, COLLATE, DEFAULT, DESCRIPTION + } + + public String getName(); + + /** + * Returns the full name as . + */ + public String getFullName(); + + public OProperty setName(String iName); + + public void set(ATTRIBUTES attribute, Object iValue); + + public OType getType(); + + /** + * Returns the linked class in lazy mode because while unmarshalling the class could be not loaded yet. + * + * @return + */ + public OClass getLinkedClass(); + + public OProperty setLinkedClass(OClass oClass); + + public OType getLinkedType(); + + public OProperty setLinkedType(OType type); + + public boolean isNotNull(); + + public OProperty setNotNull(boolean iNotNull); + + public OCollate getCollate(); + + public OProperty setCollate(String iCollateName); + + public OProperty setCollate(OCollate collate); + + public boolean isMandatory(); + + public OProperty setMandatory(boolean mandatory); + + boolean isReadonly(); + + OProperty setReadonly(boolean iReadonly); + + /** + * Min behavior depends on the Property OType. + *

          + *

            + *
          • String : minimum length
          • + *
          • Number : minimum value
          • + *
          • date and time : minimum time in millisecond, date must be written in the storage date format
          • + *
          • binary : minimum size of the byte array
          • + *
          • List,Set,Collection : minimum size of the collection
          • + *
          + * + * @return String, can be null + */ + public String getMin(); + + /** + * @see OProperty#getMin() + * @param min + * can be null + * @return this property + */ + public OProperty setMin(String min); + + /** + * Max behavior depends on the Property OType. + *

          + *

            + *
          • String : maximum length
          • + *
          • Number : maximum value
          • + *
          • date and time : maximum time in millisecond, date must be written in the storage date format
          • + *
          • binary : maximum size of the byte array
          • + *
          • List,Set,Collection : maximum size of the collection
          • + *
          + * + * @return String, can be null + */ + public String getMax(); + + /** + * @see OProperty#getMax() + * @param max + * can be null + * @return this property + */ + public OProperty setMax(String max); + + /** + * Default value for the property; can be function + * + * @return String, can be null + */ + public String getDefaultValue(); + + /** + * @see OProperty#getDefaultValue() + * @param defaultValue + * can be null + * @return this property + */ + public OProperty setDefaultValue(String defaultValue); + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * + * @param iType + * One of types supported. + *
            + *
          • UNIQUE: Doesn't allow duplicates
          • + *
          • NOTUNIQUE: Allow duplicates
          • + *
          • FULLTEXT: Indexes single word for full text search
          • + *
          + * @return see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)}. + */ + public OIndex createIndex(final OClass.INDEX_TYPE iType); + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * + * @param iType + * @return see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)}. + */ + public OIndex createIndex(final String iType); + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * + * @param iType + * One of types supported. + *
            + *
          • UNIQUE: Doesn't allow duplicates
          • + *
          • NOTUNIQUE: Allow duplicates
          • + *
          • FULLTEXT: Indexes single word for full text search
          • + *
          + * @param metadata the index metadata + * @return see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)}. + */ + public OIndex createIndex(String iType, ODocument metadata); + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * + * @param iType + * One of types supported. + *
            + *
          • UNIQUE: Doesn't allow duplicates
          • + *
          • NOTUNIQUE: Allow duplicates
          • + *
          • FULLTEXT: Indexes single word for full text search
          • + *
          + * @param metadata the index metadata + * @return see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)}. + */ + public OIndex createIndex(OClass.INDEX_TYPE iType, ODocument metadata); + + /** + * Remove the index on property + * + * @return + * @deprecated Use {@link com.orientechnologies.orient.core.index.OIndexManager#dropIndex(String)} instead. + */ + @Deprecated + public OProperty dropIndexes(); + + /** + * @return All indexes in which this property participates as first key item. + * + * @deprecated Use {@link OClass#getInvolvedIndexes(String...)} instead. + */ + @Deprecated + public Set> getIndexes(); + + /** + * @return The first index in which this property participates as first key item. + * + * @deprecated Use {@link OClass#getInvolvedIndexes(String...)} instead. + */ + @Deprecated + public OIndex getIndex(); + + /** + * @return All indexes in which this property participates. + */ + public Collection> getAllIndexes(); + + /** + * Indicates whether property is contained in indexes as its first key item. If you would like to fetch all indexes or check + * property presence in other indexes use {@link #getAllIndexes()} instead. + * + * @return true if and only if this property is contained in indexes as its first key item. + * @deprecated Use {@link OClass#areIndexed(String...)} instead. + */ + @Deprecated + public boolean isIndexed(); + + public String getRegexp(); + + public OProperty setRegexp(String regexp); + + /** + * Change the type. It checks for compatibility between the change of type. + * + * @param iType + */ + public OProperty setType(final OType iType); + + public String getCustom(final String iName); + + public OProperty setCustom(final String iName, final String iValue); + + public void removeCustom(final String iName); + + public void clearCustom(); + + public Set getCustomKeys(); + + public OClass getOwnerClass(); + + public Object get(ATTRIBUTES iAttribute); + + public Integer getId(); + + public String getDescription(); + + public OProperty setDescription(String iDescription); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyAbstractDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyAbstractDelegate.java new file mode 100755 index 00000000000..eee0c76705e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyAbstractDelegate.java @@ -0,0 +1,291 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info--at--orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Set; + +/** + * Abstract Delegate for OProperty interface. + * + * @author Luca Garulli (http://www.orientechnologies.com) + */ +public class OPropertyAbstractDelegate implements OProperty { + + protected final OProperty delegate; + + public OPropertyAbstractDelegate(final OProperty delegate) { + this.delegate = delegate; + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Integer getId() { + return delegate.getId(); + } + + @Override + public String getFullName() { + return delegate.getFullName(); + } + + @Override + public OProperty setName(final String iName) { + delegate.setName(iName); + return this; + } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public OProperty setDescription(String iDescription) { + delegate.setDescription(iDescription); + return this; + } + + @Override + public void set(final ATTRIBUTES attribute, final Object iValue) { + delegate.set(attribute, iValue); + } + + @Override + public OType getType() { + return delegate.getType(); + } + + @Override + public OClass getLinkedClass() { + return delegate.getLinkedClass(); + } + + @Override + public OType getLinkedType() { + return delegate.getLinkedType(); + } + + @Override + public boolean isNotNull() { + return delegate.isNotNull(); + } + + @Override + public OProperty setNotNull(final boolean iNotNull) { + delegate.setNotNull(iNotNull); + return this; + } + + @Override + public OCollate getCollate() { + return delegate.getCollate(); + } + + @Override + public OProperty setCollate(final String iCollateName) { + delegate.setCollate(iCollateName); + return this; + } + + @Override + public boolean isMandatory() { + return delegate.isMandatory(); + } + + @Override + public OProperty setMandatory(final boolean mandatory) { + delegate.setMandatory(mandatory); + return this; + } + + @Override + public boolean isReadonly() { + return delegate.isReadonly(); + } + + @Override + public OProperty setReadonly(final boolean iReadonly) { + delegate.setReadonly(iReadonly); + return this; + } + + @Override + public String getMin() { + return delegate.getMin(); + } + + @Override + public OProperty setMin(final String min) { + delegate.setMin(min); + return this; + } + + @Override + public String getMax() { + return delegate.getMax(); + } + + @Override + public OProperty setMax(final String max) { + delegate.setMax(max); + return this; + } + + @Override + public String getDefaultValue() { + return delegate.getDefaultValue(); + } + + @Override + public OProperty setDefaultValue(final String defaultValue) { + delegate.setDefaultValue(defaultValue); + return this; + } + + @Override + public OIndex createIndex(final OClass.INDEX_TYPE iType) { + return delegate.createIndex(iType); + } + + @Override + public OIndex createIndex(final String iType) { + return delegate.createIndex(iType); + } + + @Override public OIndex createIndex(String iType, ODocument metadata) { + return delegate.createIndex(iType, metadata); + } + + @Override public OIndex createIndex(OClass.INDEX_TYPE iType, ODocument metadata) { + return delegate.createIndex(iType, metadata); + } + + @Override + public OProperty setLinkedClass(OClass oClass) { + delegate.setLinkedClass(oClass); + return this; + } + + @Override + public OProperty setLinkedType(OType type) { + delegate.setLinkedType(type); + return this; + } + + @Override + public OProperty setCollate(OCollate collate) { + delegate.setCollate(collate); + return this; + } + + @Override + @Deprecated + public OProperty dropIndexes() { + delegate.dropIndexes(); + return this; + } + + @Override + @Deprecated + public Set> getIndexes() { + return delegate.getIndexes(); + } + + @Override + @Deprecated + public OIndex getIndex() { + return delegate.getIndex(); + } + + @Override + public Collection> getAllIndexes() { + return delegate.getAllIndexes(); + } + + @Override + @Deprecated + public boolean isIndexed() { + return delegate.isIndexed(); + } + + @Override + public String getRegexp() { + return delegate.getRegexp(); + } + + @Override + public OProperty setRegexp(final String regexp) { + delegate.setRegexp(regexp); + return this; + } + + @Override + public OProperty setType(final OType iType) { + delegate.setType(iType); + return this; + } + + @Override + public String getCustom(final String iName) { + return delegate.getCustom(iName); + } + + @Override + public OProperty setCustom(final String iName, final String iValue) { + delegate.setCustom(iName, iValue); + return this; + } + + @Override + public void removeCustom(final String iName) { + delegate.removeCustom(iName); + } + + @Override + public void clearCustom() { + delegate.clearCustom(); + } + + @Override + public Set getCustomKeys() { + return delegate.getCustomKeys(); + } + + @Override + public OClass getOwnerClass() { + return delegate.getOwnerClass(); + } + + @Override + public Object get(final ATTRIBUTES iAttribute) { + return delegate.get(iAttribute); + } + + @Override + public int compareTo(final OProperty o) { + return delegate.compareTo(o); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyImpl.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyImpl.java new file mode 100755 index 00000000000..2c9ee2f5330 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OPropertyImpl.java @@ -0,0 +1,1469 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.comparator.OCaseInsentiveComparator; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCollections; +import com.orientechnologies.orient.core.annotation.OBeforeSerialization; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.storage.OAutoshardedStorage; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.OStorageProxy; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass; + +import java.text.ParseException; +import java.util.*; + +/** + * Contains the description of a persistent class property. + * + * @author Luca Garulli + */ +public class OPropertyImpl extends ODocumentWrapperNoClass implements OProperty { + private final OClassImpl owner; + + // private String name; + // private OType type; + + private OType linkedType; + private OClass linkedClass; + transient private String linkedClassName; + + private String description; + private boolean mandatory; + private boolean notNull = false; + private String min; + private String max; + private String defaultValue; + private String regexp; + private boolean readonly; + private Map customFields; + private OCollate collate = new ODefaultCollate(); + private OGlobalProperty globalRef; + + private volatile int hashCode; + + @Deprecated + OPropertyImpl(final OClassImpl owner, final String name, final OType type) { + this(owner); + // this.name = name; + // this.type = type; + } + + OPropertyImpl(final OClassImpl owner) { + document = new ODocument().setTrackingChanges(false); + this.owner = owner; + } + + OPropertyImpl(final OClassImpl owner, final ODocument document) { + this(owner); + this.document = document; + } + + public OPropertyImpl(OClassImpl oClassImpl, OGlobalProperty global) { + this(oClassImpl); + this.globalRef = global; + } + + public String getName() { + acquireSchemaReadLock(); + try { + return globalRef.getName(); + } finally { + releaseSchemaReadLock(); + } + } + + public String getFullName() { + acquireSchemaReadLock(); + try { + return owner.getName() + "." + globalRef.getName(); + } finally { + releaseSchemaReadLock(); + } + } + + public String getFullNameQuoted() { + acquireSchemaReadLock(); + try { + return "`" + owner.getName() + "`.`" + globalRef.getName() + "`"; + } finally { + releaseSchemaReadLock(); + } + } + + public OType getType() { + acquireSchemaReadLock(); + try { + return globalRef.getType(); + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setType(final OType type) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + final ODatabaseDocumentInternal database = getDatabase(); + acquireSchemaWriteLock(); + try { + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s type %s", getFullNameQuoted(), quoteString(type.toString())); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s type %s", getFullNameQuoted(), quoteString(type.toString())); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setTypeInternal(type); + } else + setTypeInternal(type); + } finally { + releaseSchemaWriteLock(); + } + owner.fireDatabaseMigration(database, globalRef.getName(), globalRef.getType()); + + return this; + } + + public int compareTo(final OProperty o) { + acquireSchemaReadLock(); + try { + return globalRef.getName().compareTo(o.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * @param iType One of types supported.
          • UNIQUE: Doesn't allow duplicates
          • NOTUNIQUE: Allow duplicates
          • + *
          • FULLTEXT: Indexes single word for full text search
          + * + * @return + * + * @see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)} instead. + */ + public OIndex createIndex(final OClass.INDEX_TYPE iType) { + return createIndex(iType.toString()); + } + + /** + * Creates an index on this property. Indexes speed up queries but slow down insert and update operations. For massive inserts we + * suggest to remove the index, make the massive insert and recreate it. + * + * @param iType + * + * @return + * + * @see {@link OClass#createIndex(String, OClass.INDEX_TYPE, String...)} instead. + */ + public OIndex createIndex(final String iType) { + acquireSchemaReadLock(); + try { + return owner.createIndex(getFullName(), iType, globalRef.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OIndex createIndex(OClass.INDEX_TYPE iType, ODocument metadata) { + return createIndex(iType.name(), metadata); + } + + @Override + public OIndex createIndex(String iType, ODocument metadata) { + acquireSchemaReadLock(); + try { + return owner.createIndex(getFullName(), iType, null, metadata, new String[] { globalRef.getName() }); + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Remove the index on property + * + * @deprecated Use {@link OIndexManager#dropIndex(String)} instead. + */ + @Deprecated + public OPropertyImpl dropIndexes() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + acquireSchemaReadLock(); + try { + final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager(); + + final ArrayList> relatedIndexes = new ArrayList>(); + for (final OIndex index : indexManager.getClassIndexes(owner.getName())) { + final OIndexDefinition definition = index.getDefinition(); + + if (OCollections.indexOf(definition.getFields(), globalRef.getName(), new OCaseInsentiveComparator()) > -1) { + if (definition instanceof OPropertyIndexDefinition) { + relatedIndexes.add(index); + } else { + throw new IllegalArgumentException( + "This operation applicable only for property indexes. " + index.getName() + " is " + index.getDefinition()); + } + } + } + + for (final OIndex index : relatedIndexes) + getDatabase().getMetadata().getIndexManager().dropIndex(index.getName()); + + return this; + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Remove the index on property + * + * @deprecated by {@link #dropIndexes()} + */ + @Deprecated + public void dropIndexesInternal() { + dropIndexes(); + } + + /** + * Returns the first index defined for the property. + * + * @deprecated Use {@link OClass#getInvolvedIndexes(String...)} instead. + */ + @Deprecated + public OIndex getIndex() { + acquireSchemaReadLock(); + try { + Set> indexes = owner.getInvolvedIndexes(globalRef.getName()); + if (indexes != null && !indexes.isEmpty()) + return indexes.iterator().next(); + return null; + } finally { + releaseSchemaReadLock(); + } + } + + /** + * @deprecated Use {@link OClass#getInvolvedIndexes(String...)} instead. + */ + @Deprecated + public Set> getIndexes() { + acquireSchemaReadLock(); + try { + return owner.getInvolvedIndexes(globalRef.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + /** + * @deprecated Use {@link OClass#areIndexed(String...)} instead. + */ + @Deprecated + public boolean isIndexed() { + acquireSchemaReadLock(); + try { + return owner.areIndexed(globalRef.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + public OClass getOwnerClass() { + return owner; + } + + public OProperty setName(final String name) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s name %s", getFullNameQuoted(), quoteString(name)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s name %s", getFullNameQuoted(), quoteString(name)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setNameInternal(name); + } else + setNameInternal(name); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + /** + * Returns the linked class in lazy mode because while unmarshalling the class could be not loaded yet. + * + * @return + */ + public OClass getLinkedClass() { + acquireSchemaReadLock(); + try { + if (linkedClass == null && linkedClassName != null) + linkedClass = owner.owner.getClass(linkedClassName); + return linkedClass; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setLinkedClass(final OClass linkedClass) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + checkSupportLinkedClass(getType()); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s linkedclass `%s`", getFullNameQuoted(), linkedClass); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s linkedclass `%s`", getFullNameQuoted(), linkedClass); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setLinkedClassInternal(linkedClass); + } else + setLinkedClassInternal(linkedClass); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + void setLinkedClassInternal(final OClass iLinkedClass) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.linkedClass = iLinkedClass; + + } finally { + releaseSchemaWriteLock(); + } + + } + + protected static void checkSupportLinkedClass(OType type) { + if (type != OType.LINK && type != OType.LINKSET && type != OType.LINKLIST && type != OType.LINKMAP && type != OType.EMBEDDED + && type != OType.EMBEDDEDSET && type != OType.EMBEDDEDLIST && type != OType.EMBEDDEDMAP && type != OType.LINKBAG) + throw new OSchemaException("Linked class is not supported for type: " + type); + } + + public OType getLinkedType() { + acquireSchemaReadLock(); + try { + return linkedType; + } finally { + releaseSchemaReadLock(); + } + } + + public OProperty setLinkedType(final OType linkedType) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + checkLinkTypeSupport(getType()); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s linkedtype %s", getFullNameQuoted(), linkedType); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s linkedtype %s", getFullNameQuoted(), linkedType); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setLinkedTypeInternal(linkedType); + } else + setLinkedTypeInternal(linkedType); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + void setLinkedTypeInternal(final OType iLinkedType) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + acquireSchemaWriteLock(); + try { + checkEmbedded(); + this.linkedType = iLinkedType; + + } finally { + releaseSchemaWriteLock(); + } + + } + + protected static void checkLinkTypeSupport(OType type) { + if (type != OType.EMBEDDEDSET && type != OType.EMBEDDEDLIST && type != OType.EMBEDDEDMAP) + throw new OSchemaException("Linked type is not supported for type: " + type); + } + + public boolean isNotNull() { + acquireSchemaReadLock(); + try { + return notNull; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setNotNull(final boolean isNotNull) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s notnull %s", getFullNameQuoted(), isNotNull); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s notnull %s", getFullNameQuoted(), isNotNull); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setNotNullInternal(isNotNull); + } else + setNotNullInternal(isNotNull); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + public boolean isMandatory() { + acquireSchemaReadLock(); + try { + return mandatory; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setMandatory(final boolean isMandatory) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s mandatory %s", getFullNameQuoted(), isMandatory); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s mandatory %s", getFullNameQuoted(), isMandatory); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setMandatoryInternal(isMandatory); + } else + setMandatoryInternal(isMandatory); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public boolean isReadonly() { + acquireSchemaReadLock(); + try { + return readonly; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setReadonly(final boolean isReadonly) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s readonly %s", getFullNameQuoted(), isReadonly); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s readonly %s", getFullNameQuoted(), isReadonly); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setReadonlyInternal(isReadonly); + } else + setReadonlyInternal(isReadonly); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public String getMin() { + acquireSchemaReadLock(); + try { + return min; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setMin(final String min) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s min %s", getFullNameQuoted(), quoteString(min)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s min %s", getFullNameQuoted(), quoteString(min)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setMinInternal(min); + } else + setMinInternal(min); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public String getMax() { + acquireSchemaReadLock(); + try { + return max; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setMax(final String max) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s max %s", getFullNameQuoted(), quoteString(max)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s max %s", getFullNameQuoted(), quoteString(max)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setMaxInternal(max); + } else + setMaxInternal(max); + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + private static Object quoteString(String s) { + if (s == null) { + return "null"; + } + String result = "\"" + (s.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"")) + "\""; + return result; + } + + public String getDefaultValue() { + acquireSchemaReadLock(); + try { + return defaultValue; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setDefaultValue(final String defaultValue) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s default %s", getFullNameQuoted(), quoteString(defaultValue)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s default %s", getFullNameQuoted(), quoteString(defaultValue)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setDefaultValueInternal(defaultValue); + } else { + setDefaultValueInternal(defaultValue); + } + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public String getRegexp() { + acquireSchemaReadLock(); + try { + return regexp; + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setRegexp(final String regexp) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s regexp %s", getFullNameQuoted(), quoteString(regexp)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s regexp %s", getFullNameQuoted(), quoteString(regexp)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setRegexpInternal(regexp); + } else + setRegexpInternal(regexp); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + public String getCustom(final String iName) { + acquireSchemaReadLock(); + try { + if (customFields == null) + return null; + + return customFields.get(iName); + } finally { + releaseSchemaReadLock(); + } + } + + public OPropertyImpl setCustom(final String name, final String value) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final String cmd = String.format("alter property %s custom %s=%s", getFullNameQuoted(), name, quoteString(value)); + + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(commandSQL).execute(); + + setCustomInternal(name, value); + } else + setCustomInternal(name, value); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + public Map getCustomInternal() { + acquireSchemaReadLock(); + try { + if (customFields != null) + return Collections.unmodifiableMap(customFields); + return null; + } finally { + releaseSchemaReadLock(); + } + } + + public void removeCustom(final String iName) { + setCustom(iName, null); + } + + public void clearCustom() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + final String cmd = String.format("alter property %s custom clear", getFullNameQuoted()); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + + if (storage instanceof OStorageProxy) { + database.command(commandSQL).execute(); + } else if (isDistributedCommand()) { + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + database.command(commandSQL).execute(); + + clearCustomInternal(); + } else + clearCustomInternal(); + + } finally { + releaseSchemaWriteLock(); + } + } + + public Set getCustomKeys() { + acquireSchemaReadLock(); + try { + if (customFields != null) + return customFields.keySet(); + + return new HashSet(); + } finally { + releaseSchemaReadLock(); + } + } + + public Object get(final ATTRIBUTES attribute) { + if (attribute == null) + throw new IllegalArgumentException("attribute is null"); + + switch (attribute) { + case LINKEDCLASS: + return getLinkedClass(); + case LINKEDTYPE: + return getLinkedType(); + case MIN: + return getMin(); + case MANDATORY: + return isMandatory(); + case READONLY: + return isReadonly(); + case MAX: + return getMax(); + case DEFAULT: + return getDefaultValue(); + case NAME: + return getName(); + case NOTNULL: + return isNotNull(); + case REGEXP: + return getRegexp(); + case TYPE: + return getType(); + case COLLATE: + return getCollate(); + case DESCRIPTION: + return getDescription(); + } + + throw new IllegalArgumentException("Cannot find attribute '" + attribute + "'"); + } + + public void set(final ATTRIBUTES attribute, final Object iValue) { + if (attribute == null) + throw new IllegalArgumentException("attribute is null"); + + final String stringValue = iValue != null ? iValue.toString() : null; + + switch (attribute) { + case LINKEDCLASS: + setLinkedClass(getDatabase().getMetadata().getSchema().getClass(stringValue)); + break; + case LINKEDTYPE: + if (stringValue == null) + setLinkedType(null); + else + setLinkedType(OType.valueOf(stringValue)); + break; + case MIN: + setMin(stringValue); + break; + case MANDATORY: + setMandatory(Boolean.parseBoolean(stringValue)); + break; + case READONLY: + setReadonly(Boolean.parseBoolean(stringValue)); + break; + case MAX: + setMax(stringValue); + break; + case DEFAULT: + setDefaultValue(stringValue); + break; + case NAME: + setName(stringValue); + break; + case NOTNULL: + setNotNull(Boolean.parseBoolean(stringValue)); + break; + case REGEXP: + setRegexp(stringValue); + break; + case TYPE: + setType(OType.valueOf(stringValue.toUpperCase(Locale.ENGLISH))); + break; + case COLLATE: + setCollate(stringValue); + break; + case CUSTOM: + int indx = stringValue != null ? stringValue.indexOf('=') : -1; + if (indx < 0) { + if ("clear".equalsIgnoreCase(stringValue)) { + clearCustom(); + } else + throw new IllegalArgumentException("Syntax error: expected = or clear, instead found: " + iValue); + } else { + String customName = stringValue.substring(0, indx).trim(); + String customValue = stringValue.substring(indx + 1).trim(); + if (isQuoted(customValue)) { + customValue = removeQuotes(customValue); + } + if (customValue.isEmpty()) + removeCustom(customName); + else + setCustom(customName, customValue); + } + break; + case DESCRIPTION: + setDescription(stringValue); + break; + } + } + + private String removeQuotes(String s) { + s = s.trim(); + return s.substring(1, s.length() - 1); + } + + private boolean isQuoted(String s) { + s = s.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) + return true; + if (s.startsWith("'") && s.endsWith("'")) + return true; + if (s.startsWith("`") && s.endsWith("`")) + return true; + + return false; + } + + public OCollate getCollate() { + acquireSchemaReadLock(); + try { + return collate; + } finally { + releaseSchemaReadLock(); + } + } + + public OProperty setCollate(final OCollate collate) { + setCollate(collate.getName()); + return this; + } + + public OProperty setCollate(String collate) { + if (collate == null) + collate = ODefaultCollate.NAME; + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + final String cmd = String.format("alter property %s collate %s", getFullNameQuoted(), quoteString(collate)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + + if (storage instanceof OStorageProxy) { + database.command(commandSQL).execute(); + } else if (isDistributedCommand()) { + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + database.command(commandSQL).execute(); + + setCollateInternal(collate); + } else + setCollateInternal(collate); + + } finally { + releaseSchemaWriteLock(); + } + + return this; + } + + @Override + public String getDescription() { + acquireSchemaReadLock(); + try { + return description; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OPropertyImpl setDescription(final String iDescription) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + + if (storage instanceof OStorageProxy) { + final String cmd = String.format("alter property %s description %s", getFullNameQuoted(), quoteString(iDescription)); + database.command(new OCommandSQL(cmd)).execute(); + } else if (isDistributedCommand()) { + final String cmd = String.format("alter property %s description %s", getFullNameQuoted(), quoteString(iDescription)); + final OCommandSQL commandSQL = new OCommandSQL(cmd); + commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId()); + + database.command(new OCommandSQL(cmd)).execute(); + + setDescriptionInternal(iDescription); + } else + setDescriptionInternal(iDescription); + + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + @Override + public String toString() { + acquireSchemaReadLock(); + try { + return globalRef.getName() + " (type=" + globalRef.getType() + ")"; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public int hashCode() { + int sh = hashCode; + if (sh != 0) + return sh; + + acquireSchemaReadLock(); + try { + sh = hashCode; + if (sh != 0) + return sh; + + calculateHashCode(); + return hashCode; + } finally { + releaseSchemaReadLock(); + } + } + + private void calculateHashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((owner == null) ? 0 : owner.hashCode()); + hashCode = result; + } + + @Override + public boolean equals(final Object obj) { + acquireSchemaReadLock(); + try { + if (this == obj) + return true; + if (obj == null || !OProperty.class.isAssignableFrom(obj.getClass())) + return false; + OProperty other = (OProperty) obj; + if (owner == null) { + if (other.getOwnerClass() != null) + return false; + } else if (!owner.equals(other.getOwnerClass())) + return false; + return this.getName().equals(other.getName()); + } finally { + releaseSchemaReadLock(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void fromStream() { + + String name = document.field("name"); + OType type = null; + if (document.field("type") != null) + type = OType.getById(((Integer) document.field("type")).byteValue()); + Integer globalId = document.field("globalId"); + if (globalId != null) { + globalRef = owner.owner.getGlobalPropertyById(globalId); + + if (globalRef == null) + throw new OSchemaException( + "Cannot find global property id " + globalId + " in class '" + name + "'. Property data: " + document); + } else { + if (type == null) + type = OType.ANY; + globalRef = owner.owner.findOrCreateGlobalProperty(name, type); + } + + mandatory = document.containsField("mandatory") ? (Boolean) document.field("mandatory") : false; + readonly = document.containsField("readonly") ? (Boolean) document.field("readonly") : false; + notNull = document.containsField("notNull") ? (Boolean) document.field("notNull") : false; + defaultValue = (String) (document.containsField("defaultValue") ? document.field("defaultValue") : null); + if (document.containsField("collate")) + collate = OSQLEngine.getCollate((String) document.field("collate")); + + min = (String) (document.containsField("min") ? document.field("min") : null); + max = (String) (document.containsField("max") ? document.field("max") : null); + regexp = (String) (document.containsField("regexp") ? document.field("regexp") : null); + linkedClassName = (String) (document.containsField("linkedClass") ? document.field("linkedClass") : null); + linkedType = document.field("linkedType") != null ? OType.getById(((Integer) document.field("linkedType")).byteValue()) : null; + customFields = (Map) (document.containsField("customFields") ? + document.field("customFields", OType.EMBEDDEDMAP) : + null); + description = (String) (document.containsField("description") ? document.field("description") : null); + } + + public Collection> getAllIndexes() { + acquireSchemaReadLock(); + try { + final Set> indexes = owner.getIndexes(); + final List> indexList = new LinkedList>(); + for (final OIndex index : indexes) { + final OIndexDefinition indexDefinition = index.getDefinition(); + if (indexDefinition.getFields().contains(globalRef.getName())) + indexList.add(index); + } + + return indexList; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + @OBeforeSerialization + public ODocument toStream() { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + + try { + document.field("name", getName()); + document.field("type", getType().id); + document.field("globalId", globalRef.getId()); + document.field("mandatory", mandatory); + document.field("readonly", readonly); + document.field("notNull", notNull); + document.field("defaultValue", defaultValue); + + document.field("min", min); + document.field("max", max); + if (regexp != null) { + document.field("regexp", regexp); + } else { + document.removeField("regexp"); + } + if (linkedType != null) + document.field("linkedType", linkedType.id); + if (linkedClass != null || linkedClassName != null) + document.field("linkedClass", linkedClass != null ? linkedClass.getName() : linkedClassName); + + document.field("customFields", customFields != null && customFields.size() > 0 ? customFields : null, OType.EMBEDDEDMAP); + if (collate != null) { + document.field("collate", collate.getName()); + } + document.field("description", description); + + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + return document; + } + + public void acquireSchemaReadLock() { + owner.acquireSchemaReadLock(); + } + + public void releaseSchemaReadLock() { + owner.releaseSchemaReadLock(); + } + + public void acquireSchemaWriteLock() { + owner.acquireSchemaWriteLock(); + } + + public void releaseSchemaWriteLock() { + calculateHashCode(); + owner.releaseSchemaWriteLock(); + } + + public void checkEmbedded() { + if (!(getDatabase().getStorage().getUnderlying() instanceof OAbstractPaginatedStorage)) + throw new OSchemaException("'Internal' schema modification methods can be used only inside of embedded database"); + } + + protected ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + private void setNameInternal(final String name) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + String oldName = this.globalRef.getName(); + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + owner.renameProperty(oldName, name); + this.globalRef = owner.owner.findOrCreateGlobalProperty(name, this.globalRef.getType()); + } finally { + releaseSchemaWriteLock(); + } + owner.firePropertyNameMigration(getDatabase(), oldName, name, this.globalRef.getType()); + } + + private void setNotNullInternal(final boolean isNotNull) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + notNull = isNotNull; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setMandatoryInternal(final boolean isMandatory) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.mandatory = isMandatory; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setReadonlyInternal(final boolean isReadonly) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.readonly = isReadonly; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setMinInternal(final String min) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + checkForDateFormat(min); + this.min = min; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setDefaultValueInternal(final String defaultValue) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.defaultValue = defaultValue; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setMaxInternal(final String max) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + checkForDateFormat(max); + this.max = max; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setRegexpInternal(final String regexp) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + this.regexp = regexp; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setDescriptionInternal(final String iDescription) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + this.description = iDescription; + } finally { + releaseSchemaWriteLock(); + } + } + + private void setCustomInternal(final String iName, final String iValue) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + if (customFields == null) + customFields = new HashMap(); + if (iValue == null || "null".equalsIgnoreCase(iValue)) + customFields.remove(iName); + else + customFields.put(iName, iValue); + } finally { + releaseSchemaWriteLock(); + } + } + + private void clearCustomInternal() { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + customFields = null; + } finally { + releaseSchemaWriteLock(); + } + + } + + /** + * Change the type. It checks for compatibility between the change of type. + * + * @param iType + */ + private void setTypeInternal(final OType iType) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE); + + acquireSchemaWriteLock(); + try { + if (iType == globalRef.getType()) + // NO CHANGES + return; + + if (!iType.getCastable().contains(globalRef.getType())) + throw new IllegalArgumentException("Cannot change property type from " + globalRef.getType() + " to " + iType); + + this.globalRef = owner.owner.findOrCreateGlobalProperty(this.globalRef.getName(), iType); + } finally { + releaseSchemaWriteLock(); + } + } + + private OProperty setCollateInternal(String iCollate) { + acquireSchemaWriteLock(); + try { + checkEmbedded(); + + final OCollate oldCollate = this.collate; + + if (iCollate == null) + iCollate = ODefaultCollate.NAME; + + collate = OSQLEngine.getCollate(iCollate); + + if ((this.collate != null && !this.collate.equals(oldCollate)) || (this.collate == null && oldCollate != null)) { + final Set> indexes = owner.getClassIndexes(); + final List> indexesToRecreate = new ArrayList>(); + + for (OIndex index : indexes) { + OIndexDefinition definition = index.getDefinition(); + + final List fields = definition.getFields(); + if (fields.contains(getName())) + indexesToRecreate.add(index); + } + + if (!indexesToRecreate.isEmpty()) { + OLogManager.instance().info(this, "Collate value was changed, following indexes will be rebuilt %s", indexesToRecreate); + + final ODatabaseDocument database = getDatabase(); + final OIndexManager indexManager = database.getMetadata().getIndexManager(); + + for (OIndex indexToRecreate : indexesToRecreate) { + final OIndexMetadata indexMetadata = indexToRecreate.getInternal().loadMetadata(indexToRecreate.getConfiguration()); + + final ODocument metadata = indexToRecreate.getMetadata(); + final List fields = indexMetadata.getIndexDefinition().getFields(); + final String[] fieldsToIndex = fields.toArray(new String[fields.size()]); + + indexManager.dropIndex(indexMetadata.getName()); + owner.createIndex(indexMetadata.getName(), indexMetadata.getType(), null, metadata, indexMetadata.getAlgorithm(), + fieldsToIndex); + } + } + } + } finally { + releaseSchemaWriteLock(); + } + return this; + } + + private void checkForDateFormat(final String iDateAsString) { + if (iDateAsString != null) + if (globalRef.getType() == OType.DATE) { + try { + getDatabase().getStorage().getConfiguration().getDateFormatInstance().parse(iDateAsString); + } catch (ParseException e) { + throw OException + .wrapException(new OSchemaException("Invalid date format while formatting date '" + iDateAsString + "'"), e); + } + } else if (globalRef.getType() == OType.DATETIME) { + try { + getDatabase().getStorage().getConfiguration().getDateTimeFormatInstance().parse(iDateAsString); + } catch (ParseException e) { + throw OException + .wrapException(new OSchemaException("Invalid datetime format while formatting date '" + iDateAsString + "'"), e); + } + } + } + + private boolean isDistributedCommand() { + return getDatabase().getStorage() instanceof OAutoshardedStorage && !OScenarioThreadLocal.INSTANCE.isRunModeDistributed(); + } + + @Override + public Integer getId() { + return globalRef.getId(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchema.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchema.java new file mode 100644 index 00000000000..bd2a5eb5f15 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchema.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionFactory; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public interface OSchema { + + int countClasses(); + + OClass createClass(Class iClass); + + OClass createClass(String iClassName); + + OClass createClass(String iClassName, OClass iSuperClass); + + OClass createClass(String className, int clusters, OClass... superClasses); + + OClass createClass(String iClassName, OClass... superClasses); + + OClass createClass(String iClassName, OClass iSuperClass, int[] iClusterIds); + + OClass createClass(String className, int[] clusterIds, OClass... superClasses); + + OClass createAbstractClass(Class iClass); + + OClass createAbstractClass(String iClassName); + + OClass createAbstractClass(String iClassName, OClass iSuperClass); + + OClass createAbstractClass(String iClassName, OClass... superClasses); + + void dropClass(String iClassName); + + RET reload(); + + boolean existsClass(String iClassName); + + OClass getClass(Class iClass); + + /** + * Returns the OClass instance by class name. + *

          + * If the class is not configured and the database has an entity manager with the requested class as registered, then creates a + * schema class for it at the fly. + *

          + * If the database nor the entity manager have not registered class with specified name, returns null. + * + * @param iClassName Name of the class to retrieve + * @return class instance or null if class with given name is not configured. + */ + OClass getClass(String iClassName); + + OClass getOrCreateClass(String iClassName); + + OClass getOrCreateClass(String iClassName, OClass iSuperClass); + + OClass getOrCreateClass(String iClassName, OClass... superClasses); + + Collection getClasses(); + + void create(); + + @Deprecated + int getVersion(); + + ORID getIdentity(); + + /** + * Do nothing. Starting from 1.0rc2 the schema is auto saved! + * + * @COMPATIBILITY 1.0rc1 + */ + @Deprecated + RET save(); + + /** + * Returns all the classes that rely on a cluster + * + * @param iClusterName Cluster name + */ + Set getClassesRelyOnCluster(String iClusterName); + + OClass getClassByClusterId(int clusterId); + + OGlobalProperty getGlobalPropertyById(int id); + + List getGlobalProperties(); + + OGlobalProperty createGlobalProperty(String name, OType type, Integer id); + + OClusterSelectionFactory getClusterSelectionFactory(); + + OImmutableSchema makeSnapshot(); + + /** + * Callback invoked when the schema is loaded, after all the initializations. + */ + void onPostIndexManagement(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaProxy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaProxy.java new file mode 100644 index 00000000000..9aa460857e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaProxy.java @@ -0,0 +1,259 @@ +/* + * + * * Co + * yright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OProxedResource; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionFactory; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * Proxy class to use the shared OSchemaShared instance. Before to delegate each operations it sets the current database in the + * thread local. + * + * @author Luca + * + */ +@SuppressWarnings("unchecked") +public class OSchemaProxy extends OProxedResourceimplements OSchema { + + public OSchemaProxy(final OSchemaShared iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + @Override + public OImmutableSchema makeSnapshot() { + return delegate.makeSnapshot(); + } + + public void create() { + delegate.create(); + } + + public int countClasses() { + return delegate.countClasses(); + } + + public OClass createClass(final Class iClass) { + return delegate.createClass(iClass); + } + + public OClass createClass(final String iClassName) { + + return delegate.createClass(iClassName); + } + + public OClass getOrCreateClass(final String iClassName) { + return getOrCreateClass(iClassName, (OClass) null); + } + + public OClass getOrCreateClass(final String iClassName, final OClass iSuperClass) { + if (iClassName == null) + return null; + + OClass cls = delegate.getClass(iClassName.toLowerCase(Locale.ENGLISH)); + if (cls != null) + return cls; + + cls = delegate.getOrCreateClass(iClassName, iSuperClass); + + return cls; + } + + @Override + public OClass getOrCreateClass(String iClassName, OClass... superClasses) { + + return delegate.getOrCreateClass(iClassName, superClasses); + } + + @Override + public OClass createClass(final String iClassName, final OClass iSuperClass) { + + return delegate.createClass(iClassName, iSuperClass, (int[]) null); + } + + @Override + public OClass createClass(String iClassName, OClass... superClasses) { + + return delegate.createClass(iClassName, superClasses); + } + + public OClass createClass(final String iClassName, final OClass iSuperClass, final int[] iClusterIds) { + + return delegate.createClass(iClassName, iSuperClass, iClusterIds); + } + + @Override + public OClass createClass(String className, int[] clusterIds, OClass... superClasses) { + + return delegate.createClass(className, clusterIds, superClasses); + } + + @Override + public OClass createAbstractClass(final Class iClass) { + + return delegate.createAbstractClass(iClass); + } + + @Override + public void onPostIndexManagement() { + delegate.onPostIndexManagement(); + } + + @Override + public OClass createAbstractClass(final String iClassName) { + + return delegate.createAbstractClass(iClassName); + } + + @Override + public OClass createAbstractClass(final String iClassName, final OClass iSuperClass) { + + return delegate.createAbstractClass(iClassName, iSuperClass); + } + + @Override + public OClass createAbstractClass(String iClassName, OClass... superClasses) { + + return delegate.createAbstractClass(iClassName, superClasses); + } + + public void dropClass(final String iClassName) { + + delegate.dropClass(iClassName); + } + + public boolean existsClass(final String iClassName) { + if (iClassName == null) + return false; + + return delegate.existsClass(iClassName.toLowerCase(Locale.ENGLISH)); + } + + public OClass getClass(final Class iClass) { + if (iClass == null) + return null; + + return delegate.getClass(iClass); + } + + public OClass getClass(final String iClassName) { + if (iClassName == null) + return null; + + return delegate.getClass(iClassName); + } + + public Collection getClasses() { + return delegate.getClasses(); + } + + public void load() { + + delegate.load(); + + } + + public RET reload() { + + delegate.reload(); + + return (RET) delegate; + } + + public RET save() { + + return (RET) delegate.save(); + } + + public int getVersion() { + + return delegate.getVersion(); + } + + public ORID getIdentity() { + + return delegate.getIdentity(); + } + + public void close() { + } + + public String toString() { + + return delegate.toString(); + } + + @Override + public Set getClassesRelyOnCluster(final String iClusterName) { + return delegate.getClassesRelyOnCluster(iClusterName); + } + + @Override + public OClass createClass(String className, int clusters, OClass... superClasses) { + return delegate.createClass(className, clusters, superClasses); + } + + @Override + public OClass getClassByClusterId(int clusterId) { + return delegate.getClassByClusterId(clusterId); + } + + @Override + public OGlobalProperty getGlobalPropertyById(int id) { + return delegate.getGlobalPropertyById(id); + } + + @Override + public List getGlobalProperties() { + return delegate.getGlobalProperties(); + } + + public OGlobalProperty createGlobalProperty(String name, OType type, Integer id) { + return delegate.createGlobalProperty(name, type, id); + } + + + @Override + public OClusterSelectionFactory getClusterSelectionFactory() { + return delegate.getClusterSelectionFactory(); + } + + + public Set getBlobClusters() { + return delegate.getBlobClusters(); + } + + public int addBlobCluster(final int clusterId) { + return delegate.addBlobCluster(clusterId); + } + + public void removeBlobCluster(String clusterName){ + delegate.removeBlobCluster(clusterName); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaShared.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaShared.java new file mode 100755 index 00000000000..92f11107131 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OSchemaShared.java @@ -0,0 +1,1448 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.concur.lock.OReadersWriterSpinLock; +import com.orientechnologies.common.concur.resource.OCloseable; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.types.OModifiableInteger; +import com.orientechnologies.common.util.OArrays; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.annotation.OBeforeSerialization; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseLifecycleListener; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.exception.OSchemaNotCreatedException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexManager; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionFactory; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.storage.OAutoshardedStorage; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.OStorageProxy; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.type.ODocumentWrapper; +import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass; + +import java.util.*; +import java.util.concurrent.Callable; + +/** + * Shared schema class. It's shared by all the database instances that point to the same storage. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OSchemaShared extends ODocumentWrapperNoClass + implements OSchema, OCloseable, OOrientStartupListener, OOrientShutdownListener { + private static final int NOT_EXISTENT_CLUSTER_ID = -1; + public static final int CURRENT_VERSION_NUMBER = 4; + public static final int VERSION_NUMBER_V4 = 4; + // this is needed for guarantee the compatibility to 2.0-M1 and 2.0-M2 no changed associated with it + public static final int VERSION_NUMBER_V5 = 5; + private static final long serialVersionUID = 1L; + + private final boolean clustersCanNotBeSharedAmongClasses; + + private final OReadersWriterSpinLock rwSpinLock = new OReadersWriterSpinLock(); + + private final Map classes = new HashMap(); + private final Map clustersToClasses = new HashMap(); + + private final OClusterSelectionFactory clusterSelectionFactory = new OClusterSelectionFactory(); + + private volatile ThreadLocal modificationCounter = new OModificationsCounter(); + private final List properties = new ArrayList(); + private final Map propertiesByNameType = new HashMap(); + private Set blobClusters = new HashSet(); + private volatile int version = 0; + private volatile OImmutableSchema snapshot; + + private static Set internalClasses = new HashSet(); + + static { + internalClasses.add("ouser"); + internalClasses.add("orole"); + internalClasses.add("oidentity"); + internalClasses.add("ofunction"); + internalClasses.add("osequence"); + internalClasses.add("otrigger"); + internalClasses.add("oschedule"); + internalClasses.add("orids"); + } + + private static final class ClusterIdsAreEmptyException extends Exception { + } + + public OSchemaShared(boolean clustersCanNotBeSharedAmongClasses) { + super(new ODocument().setTrackingChanges(false)); + this.clustersCanNotBeSharedAmongClasses = clustersCanNotBeSharedAmongClasses; + + Orient.instance().registerWeakOrientStartupListener(this); + Orient.instance().registerWeakOrientShutdownListener(this); + } + + @Override + public void onShutdown() { + modificationCounter = null; + } + + @Override + public void onStartup() { + if (modificationCounter == null) + modificationCounter = new OModificationsCounter(); + } + + public static Character checkClassNameIfValid(String iName) throws OSchemaException { + if (iName == null) + throw new IllegalArgumentException("Name is null"); + + iName = iName.trim(); + + final int nameSize = iName.length(); + + if (nameSize == 0) + throw new IllegalArgumentException("Name is empty"); + + for (int i = 0; i < nameSize; ++i) { + final char c = iName.charAt(i); + if (c == ':' || c == ',' || c == ';' || c == ' ' || c == '@' || c == '=' || c == '.' || c == '#') + // INVALID CHARACTER + return c; + } + + return null; + } + + public static Character checkFieldNameIfValid(String iName) { + if (iName == null) + throw new IllegalArgumentException("Name is null"); + + iName = iName.trim(); + + final int nameSize = iName.length(); + + if (nameSize == 0) + throw new IllegalArgumentException("Name is empty"); + + for (int i = 0; i < nameSize; ++i) { + final char c = iName.charAt(i); + if (c == ':' || c == ',' || c == ';' || c == ' ' || c == '=') + // INVALID CHARACTER + return c; + } + + return null; + } + + @Override + public OImmutableSchema makeSnapshot() { + if (snapshot == null) { + // Is null only in the case that is asked while the schema is created + // all the other cases are already protected by a write lock + acquireSchemaReadLock(); + try { + if (snapshot == null) + snapshot = new OImmutableSchema(this); + } finally { + releaseSchemaReadLock(); + } + } + return snapshot; + } + + public boolean isClustersCanNotBeSharedAmongClasses() { + return clustersCanNotBeSharedAmongClasses; + } + + public OClusterSelectionFactory getClusterSelectionFactory() { + return clusterSelectionFactory; + } + + public int countClasses() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + acquireSchemaReadLock(); + try { + return classes.size(); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public void onPostIndexManagement() { + for (OClass c : classes.values()) { + if (c instanceof OClassImpl) + ((OClassImpl) c).onPostIndexManagement(); + } + } + + public OClass createClass(final Class clazz) { + OClass result = null; + + int[] clusterIds = null; + int retry = 0; + + while (true) + try { + acquireSchemaWriteLock(); + try { + // TODO: revisit this logic: interfaces should be also taken into concederation + // TODO: Remove code duplication of this kind! + final Class superClass = clazz.getSuperclass(); + final OClass cls; + if (superClass != null && superClass != Object.class && existsClass(superClass.getSimpleName())) + cls = getClass(superClass.getSimpleName()); + else + cls = null; + + result = doCreateClass(clazz.getSimpleName(), clusterIds, retry, cls); + break; + } finally { + releaseSchemaWriteLock(); + } + + } catch (ClusterIdsAreEmptyException e) { + clusterIds = createClusters(clazz.getSimpleName()); + retry++; + } + + return result; + } + + @Override + public OClass createClass(final String className) { + return createClass(className, (OClass) null, (int[]) null); + } + + @Override + public OClass createClass(final String iClassName, final OClass iSuperClass) { + return createClass(iClassName, iSuperClass, (int[]) null); + } + + @Override + public OClass createClass(String iClassName, OClass... superClasses) { + return createClass(iClassName, (int[]) null, superClasses); + } + + @Override + public OClass getOrCreateClass(final String iClassName) { + return getOrCreateClass(iClassName, (OClass) null); + } + + @Override + public OClass getOrCreateClass(final String iClassName, final OClass superClass) { + return getOrCreateClass(iClassName, superClass == null ? new OClass[0] : new OClass[] { superClass }); + } + + @Override + public OClass getOrCreateClass(final String iClassName, final OClass... superClasses) { + if (iClassName == null) + return null; + + acquireSchemaReadLock(); + try { + OClass cls = classes.get(iClassName.toLowerCase(Locale.ENGLISH)); + if (cls != null) + return cls; + } finally { + releaseSchemaReadLock(); + } + + OClass cls; + + int[] clusterIds = null; + int retry = 0; + + while (true) + try { + acquireSchemaWriteLock(); + try { + cls = classes.get(iClassName.toLowerCase(Locale.ENGLISH)); + if (cls != null) + return cls; + + cls = doCreateClass(iClassName, clusterIds, retry, superClasses); + // TODO: revisit this exception + // if (superClass != null && !cls.isSubClassOf(superClass)) + // throw new IllegalArgumentException("Class '" + iClassName + "' is not an instance of " + superClass.getShortName()); + + addClusterClassMap(cls); + } finally { + releaseSchemaWriteLock(); + } + break; + } catch (ClusterIdsAreEmptyException e) { + clusterIds = createClusters(iClassName); + retry++; + } + + return cls; + } + + @Override + public OClass createAbstractClass(final Class iClass) { + OClass cls; + int[] clusterIds = new int[] { -1 }; + int retry = 0; + + while (true) + try { + acquireSchemaWriteLock(); + try { + // TODO: revisit this logic: interfaces should be also taken into concederation + final Class superClass = iClass.getSuperclass(); + if (superClass != null && superClass != Object.class && existsClass(superClass.getSimpleName())) + cls = getClass(superClass.getSimpleName()); + else + cls = null; + cls = doCreateClass(iClass.getSimpleName(), clusterIds, retry, cls); + } finally { + releaseSchemaWriteLock(); + } + + break; + } catch (ClusterIdsAreEmptyException e) { + clusterIds = createClusters(iClass.getSimpleName()); + retry++; + } + + return cls; + } + + @Override + public OClass createAbstractClass(final String className) { + return createClass(className, null, new int[] { -1 }); + } + + @Override + public OClass createAbstractClass(final String className, final OClass superClass) { + return createClass(className, superClass, new int[] { -1 }); + } + + @Override + public OClass createAbstractClass(String iClassName, OClass... superClasses) { + return createClass(iClassName, new int[] { -1 }, superClasses); + } + + @Override + public OClass createClass(final String className, final OClass superClass, int[] clusterIds) { + return createClass(className, clusterIds, superClass); + } + + @Override + public OClass createClass(final String className, int[] clusterIds, OClass... superClasses) { + final Character wrongCharacter = OSchemaShared.checkClassNameIfValid(className); + if (wrongCharacter != null) + throw new OSchemaException( + "Invalid class name found. Character '" + wrongCharacter + "' cannot be used in class name '" + className + "'"); + + OClass result; + int retry = 0; + + while (true) + try { + result = doCreateClass(className, clusterIds, retry, superClasses); + break; + } catch (ClusterIdsAreEmptyException e) { + classes.remove(className.toLowerCase(Locale.ENGLISH)); + clusterIds = (int[]) OScenarioThreadLocal.executeAsDefault(new Callable() { + @Override + public int[] call() throws Exception { + return createClusters(className); + } + }); + + retry++; + } + + return result; + } + + @Override + public OClass createClass(final String className, int clusters, OClass... superClasses) { + final Character wrongCharacter = OSchemaShared.checkClassNameIfValid(className); + if (wrongCharacter != null) + throw new OSchemaException( + "Invalid class name found. Character '" + wrongCharacter + "' cannot be used in class name '" + className + "'"); + + return doCreateClass(className, clusters, 1, superClasses); + } + + public void checkEmbedded(final OStorage storage) { + if (!(storage.getUnderlying() instanceof OAbstractPaginatedStorage)) + throw new OSchemaException("'Internal' schema modification methods can be used only inside of embedded database"); + } + + void addClusterForClass(final int clusterId, final OClass cls) { + acquireSchemaWriteLock(); + try { + if (!clustersCanNotBeSharedAmongClasses) + return; + + if (clusterId < 0) + return; + + final OStorage storage = getDatabase().getStorage(); + checkEmbedded(storage); + + final OClass existingCls = clustersToClasses.get(clusterId); + if (existingCls != null && !cls.equals(existingCls)) + throw new OSchemaException( + "Cluster with id " + clusterId + " already belongs to class " + clustersToClasses.get(clusterId)); + + clustersToClasses.put(clusterId, cls); + } finally { + releaseSchemaWriteLock(); + } + } + + void removeClusterForClass(int clusterId, OClass cls) { + acquireSchemaWriteLock(); + try { + if (!clustersCanNotBeSharedAmongClasses) + return; + + if (clusterId < 0) + return; + + final OStorage storage = getDatabase().getStorage(); + checkEmbedded(storage); + + clustersToClasses.remove(clusterId); + } finally { + releaseSchemaWriteLock(); + } + } + + void checkClusterCanBeAdded(int clusterId, OClass cls) { + acquireSchemaReadLock(); + try { + if (!clustersCanNotBeSharedAmongClasses) + return; + + if (clusterId < 0) + return; + + if (blobClusters.contains(clusterId)) + throw new OSchemaException("Cluster with id " + clusterId + " already belongs to Blob"); + + final OClass existingCls = clustersToClasses.get(clusterId); + + if (existingCls != null && (cls == null || !cls.equals(existingCls))) + throw new OSchemaException( + "Cluster with id " + clusterId + " already belongs to the class '" + clustersToClasses.get(clusterId) + "'"); + + } finally { + releaseSchemaReadLock(); + } + } + + public OClass getClassByClusterId(int clusterId) { + acquireSchemaReadLock(); + try { + if (!clustersCanNotBeSharedAmongClasses) + throw new OSchemaException("This feature is not supported in current version of binary format."); + + return clustersToClasses.get(clusterId); + } finally { + releaseSchemaReadLock(); + } + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.metadata.schema.OSchema#dropClass(java.lang.String) + */ + public void dropClass(final String className) { + final ODatabaseDocumentInternal db = getDatabase(); + final OStorage storage = db.getStorage(); + final StringBuilder cmd; + + acquireSchemaWriteLock(); + try { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot drop a class inside a transaction"); + + if (className == null) + throw new IllegalArgumentException("Class name is null"); + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + final String key = className.toLowerCase(Locale.ENGLISH); + + OClass cls = classes.get(key); + + if (cls == null) + throw new OSchemaException("Class '" + className + "' was not found in current database"); + + if (!cls.getSubclasses().isEmpty()) + throw new OSchemaException("Class '" + className + "' cannot be dropped because it has sub classes " + cls.getSubclasses() + + ". Remove the dependencies before trying to drop it again"); + + cmd = new StringBuilder("drop class "); + cmd.append(className); + cmd.append(" unsafe"); + + if (executeThroughDistributedStorage()) { + final OAutoshardedStorage autoshardedStorage = (OAutoshardedStorage) storage; + OCommandSQL commandSQL = new OCommandSQL(cmd.toString()); + commandSQL.addExcludedNode(autoshardedStorage.getNodeId()); + db.command(commandSQL).execute(); + + dropClassInternal(className); + } else if (storage instanceof OStorageProxy) { + final OCommandSQL commandSQL = new OCommandSQL(cmd.toString()); + db.command(commandSQL).execute(); + final OClass classToDrop = getClass(className); + reload(); + if (getClass(className) == null) // really dropped, for example there may be no rights to drop a class + dropClassIndexes(classToDrop); + } else + dropClassInternal(className); + + // FREE THE RECORD CACHE + getDatabase().getLocalCache().freeCluster(cls.getDefaultClusterId()); + + } finally { + releaseSchemaWriteLock(); + } + } + + /** + * Reloads the schema inside a storage's shared lock. + */ + @Override + public RET reload() { + rwSpinLock.acquireWriteLock(); + try { + reload(null); + snapshot = new OImmutableSchema(this); + return (RET) this; + } finally { + rwSpinLock.releaseWriteLock(); + } + } + + public boolean existsClass(final String iClassName) { + if (iClassName == null) + return false; + + acquireSchemaReadLock(); + try { + return classes.containsKey(iClassName.toLowerCase(Locale.ENGLISH)); + } finally { + releaseSchemaReadLock(); + } + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.metadata.schema.OSchema#getClass(java.lang.Class) + */ + public OClass getClass(final Class iClass) { + if (iClass == null) + return null; + + return getClass(iClass.getSimpleName()); + } + + /* + * (non-Javadoc) + * + * @see com.orientechnologies.orient.core.metadata.schema.OSchema#getClass(java.lang.String) + */ + public OClass getClass(final String iClassName) { + if (iClassName == null) + return null; + + acquireSchemaReadLock(); + try { + return classes.get(iClassName.toLowerCase(Locale.ENGLISH)); + } finally { + releaseSchemaReadLock(); + } + } + + public void acquireSchemaReadLock() { + rwSpinLock.acquireReadLock(); + } + + public void releaseSchemaReadLock() { + rwSpinLock.releaseReadLock(); + } + + public void acquireSchemaWriteLock() { + rwSpinLock.acquireWriteLock(); + modificationCounter.get().increment(); + } + + public void releaseSchemaWriteLock() { + releaseSchemaWriteLock(true); + } + + public void releaseSchemaWriteLock(final boolean iSave) { + try { + if (modificationCounter.get().intValue() == 1) { + // if it is embedded storage modification of schema is done by internal methods otherwise it is done by + // by sql commands and we need to reload local replica + + if (iSave) + if (getDatabase().getStorage().getUnderlying() instanceof OAbstractPaginatedStorage) + saveInternal(); + else + reload(); + else + snapshot = new OImmutableSchema(this); + + version++; + } + } finally { + rwSpinLock.releaseWriteLock(); + modificationCounter.get().decrement(); + } + + assert modificationCounter.get().intValue() >= 0; + + if (modificationCounter.get().intValue() == 0 && getDatabase().getStorage().getUnderlying() instanceof OStorageProxy) { + getDatabase().getStorage().reload(); + } + } + + void changeClassName(final String oldName, final String newName, final OClass cls) { + + if (oldName != null && oldName.equalsIgnoreCase(newName)) + throw new IllegalArgumentException("Class '" + oldName + "' cannot be renamed with the same name"); + + acquireSchemaWriteLock(); + try { + checkEmbedded(getDatabase().getStorage()); + + if (newName != null && classes.containsKey(newName.toLowerCase(Locale.ENGLISH))) + throw new IllegalArgumentException("Class '" + newName + "' is already present in schema"); + + if (oldName != null) + classes.remove(oldName.toLowerCase(Locale.ENGLISH)); + if (newName != null) + classes.put(newName.toLowerCase(Locale.ENGLISH), cls); + + } finally { + releaseSchemaWriteLock(); + } + } + + /** + * Binds ODocument to POJO. + */ + @Override + public void fromStream() { + rwSpinLock.acquireWriteLock(); + modificationCounter.get().increment(); + try { + // READ CURRENT SCHEMA VERSION + final Integer schemaVersion = (Integer) document.field("schemaVersion"); + if (schemaVersion == null) { + OLogManager.instance().error(this, + "Database's schema is empty! Recreating the system classes and allow the opening of the database but double check the integrity of the database"); + return; + } else if (schemaVersion != CURRENT_VERSION_NUMBER && VERSION_NUMBER_V5 != schemaVersion) { + // VERSION_NUMBER_V5 is needed for guarantee the compatibility to 2.0-M1 and 2.0-M2 no changed associated with it + // HANDLE SCHEMA UPGRADE + throw new OConfigurationException( + "Database schema is different. Please export your old database with the previous version of OrientDB and reimport it using the current one."); + } + + properties.clear(); + propertiesByNameType.clear(); + List globalProperties = document.field("globalProperties"); + boolean hasGlobalProperties = false; + if (globalProperties != null) { + hasGlobalProperties = true; + for (ODocument oDocument : globalProperties) { + OGlobalPropertyImpl prop = new OGlobalPropertyImpl(); + prop.fromDocument(oDocument); + ensurePropertiesSize(prop.getId()); + properties.set(prop.getId(), prop); + propertiesByNameType.put(prop.getName() + "|" + prop.getType().name(), prop); + } + } + // REGISTER ALL THE CLASSES + clustersToClasses.clear(); + + final Map newClasses = new HashMap(); + + OClassImpl cls; + Collection storedClasses = document.field("classes"); + for (ODocument c : storedClasses) { + + cls = new OClassImpl(this, c, (String) c.field("name")); + cls.fromStream(); + + if (classes.containsKey(cls.getName().toLowerCase(Locale.ENGLISH))) { + cls = (OClassImpl) classes.get(cls.getName().toLowerCase(Locale.ENGLISH)); + cls.fromStream(c); + } + + newClasses.put(cls.getName().toLowerCase(Locale.ENGLISH), cls); + + if (cls.getShortName() != null) + newClasses.put(cls.getShortName().toLowerCase(Locale.ENGLISH), cls); + + addClusterClassMap(cls); + } + + classes.clear(); + classes.putAll(newClasses); + + // REBUILD THE INHERITANCE TREE + Collection superClassNames; + String legacySuperClassName; + List superClasses; + OClass superClass; + + for (ODocument c : storedClasses) { + + superClassNames = c.field("superClasses"); + legacySuperClassName = c.field("superClass"); + if (superClassNames == null) + superClassNames = new ArrayList(); + else + superClassNames = new HashSet(superClassNames); + + if (legacySuperClassName != null && !superClassNames.contains(legacySuperClassName)) + superClassNames.add(legacySuperClassName); + + if (!superClassNames.isEmpty()) { + // HAS A SUPER CLASS or CLASSES + cls = (OClassImpl) classes.get(((String) c.field("name")).toLowerCase(Locale.ENGLISH)); + superClasses = new ArrayList(superClassNames.size()); + for (String superClassName : superClassNames) { + + superClass = classes.get(superClassName.toLowerCase(Locale.ENGLISH)); + + if (superClass == null) + throw new OConfigurationException("Super class '" + superClassName + "' was declared in class '" + cls.getName() + + "' but was not found in schema. Remove the dependency or create the class to continue."); + superClasses.add(superClass); + } + cls.setSuperClassesInternal(superClasses); + } + } + + if (document.containsField("blobClusters")) + blobClusters = document.field("blobClusters"); + + if (!hasGlobalProperties) { + if (getDatabase().getStorage().getUnderlying() instanceof OAbstractPaginatedStorage) + saveInternal(); + } + + } finally { + version++; + modificationCounter.get().decrement(); + rwSpinLock.releaseWriteLock(); + } + } + + /** + * Binds POJO to ODocument. + */ + @Override + @OBeforeSerialization + public ODocument toStream() { + rwSpinLock.acquireReadLock(); + try { + document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + + try { + document.field("schemaVersion", CURRENT_VERSION_NUMBER); + + Set cc = new HashSet(); + for (OClass c : classes.values()) + cc.add(((OClassImpl) c).toStream()); + + document.field("classes", cc, OType.EMBEDDEDSET); + + List globalProperties = new ArrayList(); + for (OGlobalProperty globalProperty : properties) { + if (globalProperty != null) + globalProperties.add(((OGlobalPropertyImpl) globalProperty).toDocument()); + } + document.field("globalProperties", globalProperties, OType.EMBEDDEDLIST); + document.field("blobClusters", blobClusters, OType.EMBEDDEDSET); + } finally { + document.setInternalStatus(ORecordElement.STATUS.LOADED); + } + + return document; + } finally { + rwSpinLock.releaseReadLock(); + } + } + + public Collection getClasses() { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + acquireSchemaReadLock(); + try { + return new HashSet(classes.values()); + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public Set getClassesRelyOnCluster(final String clusterName) { + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_READ); + + acquireSchemaReadLock(); + try { + final int clusterId = getDatabase().getClusterIdByName(clusterName); + final Set result = new HashSet(); + for (OClass c : classes.values()) { + if (OArrays.contains(c.getPolymorphicClusterIds(), clusterId)) + result.add(c); + } + + return result; + } finally { + releaseSchemaReadLock(); + } + } + + @Override + public OSchemaShared load() { + + rwSpinLock.acquireWriteLock(); + try { + if (!new ORecordId(getDatabase().getStorage().getConfiguration().schemaRecordId).isValid()) + throw new OSchemaNotCreatedException("Schema is not created and cannot be loaded"); + + ((ORecordId) document.getIdentity()).fromString(getDatabase().getStorage().getConfiguration().schemaRecordId); + reload("*:-1 index:0"); + + snapshot = new OImmutableSchema(this); + + return this; + } finally { + rwSpinLock.releaseWriteLock(); + } + } + + public void create() { + rwSpinLock.acquireWriteLock(); + try { + final ODatabaseDocumentInternal db = getDatabase(); + super.save(OMetadataDefault.CLUSTER_INTERNAL_NAME); + db.getStorage().getConfiguration().schemaRecordId = document.getIdentity().toString(); + db.getStorage().getConfiguration().update(); + snapshot = new OImmutableSchema(this); + } finally { + rwSpinLock.releaseWriteLock(); + } + } + + @Override + public void close() { + classes.clear(); + clustersToClasses.clear(); + blobClusters.clear(); + properties.clear(); + document.clear(); + } + + @Deprecated + public int getVersion() { + return version; + } + + public ORID getIdentity() { + acquireSchemaReadLock(); + try { + return document.getIdentity(); + } finally { + releaseSchemaReadLock(); + } + } + + /** + * Avoid to handle this by user API. + */ + @Override + public RET save() { + return (RET) this; + } + + /** + * Avoid to handle this by user API. + */ + @Override + public RET save(final String iClusterName) { + return (RET) this; + } + + public OSchemaShared setDirty() { + rwSpinLock.acquireWriteLock(); + try { + document.setDirty(); + return this; + } finally { + rwSpinLock.releaseWriteLock(); + } + } + + public OGlobalProperty getGlobalPropertyById(int id) { + if (id >= properties.size()) + return null; + return properties.get(id); + } + + public OGlobalProperty createGlobalProperty(final String name, final OType type, final Integer id) { + OGlobalProperty global; + OLogManager.instance().error(this,"CREATING GLOB PROP " + name + " id=" + id); + + if (id < properties.size() && (global = properties.get(id)) != null) { + if (!global.getName().equals(name) || !global.getType().equals(type)) + throw new OSchemaException("A property with id " + id + " already exist "); + return global; + } + + global = new OGlobalPropertyImpl(name, type, id); + ensurePropertiesSize(id); + properties.set(id, global); + propertiesByNameType.put(global.getName() + "|" + global.getType().name(), global); + + return global; + } + + public List getGlobalProperties() { + return Collections.unmodifiableList(properties); + } + + protected OGlobalProperty findOrCreateGlobalProperty(final String name, final OType type) { + OGlobalProperty global = propertiesByNameType.get(name + "|" + type.name()); + if (global == null) { + int id = properties.size(); + global = new OGlobalPropertyImpl(name, type, id); + properties.add(id, global); + propertiesByNameType.put(global.getName() + "|" + global.getType().name(), global); + } + return global; + } + + private OClass doCreateClass(final String className, int[] clusterIds, int retry, OClass... superClasses) + throws ClusterIdsAreEmptyException { + OClass result; + + final ODatabaseDocumentInternal db = getDatabase(); + final OStorage storage = db.getStorage(); + StringBuilder cmd = null; + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_CREATE); + if (superClasses != null) + OClassImpl.checkParametersConflict(Arrays.asList(superClasses)); + + acquireSchemaWriteLock(); + try { + + final String key = className.toLowerCase(Locale.ENGLISH); + if (classes.containsKey(key) && retry == 0) + throw new OSchemaException("Class '" + className + "' already exists in current database"); + + if (!executeThroughDistributedStorage()) + checkClustersAreAbsent(clusterIds); + + if (clusterIds == null || clusterIds.length == 0) { + clusterIds = createClusters(className, getDatabase().getStorage().getConfiguration().getMinimumClusters()); + } + + cmd = new StringBuilder("create class "); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + cmd.append(className); + if (getDatabase().getStorage().getConfiguration().isStrictSql()) + cmd.append('`'); + + List superClassesList = new ArrayList(); + if (superClasses != null && superClasses.length > 0) { + boolean first = true; + for (OClass superClass : superClasses) { + // Filtering for null + if (superClass != null) { + if (first) + cmd.append(" extends "); + else + cmd.append(", "); + cmd.append('`').append(superClass.getName()).append('`'); + first = false; + superClassesList.add(superClass); + } + } + } + + if (clusterIds != null) { + if (clusterIds.length == 1 && clusterIds[0] == -1) + cmd.append(" abstract"); + else { + cmd.append(" cluster "); + for (int i = 0; i < clusterIds.length; ++i) { + if (i > 0) + cmd.append(','); + else + cmd.append(' '); + + cmd.append(clusterIds[i]); + } + } + } + + if (executeThroughDistributedStorage()) { + createClassInternal(className, clusterIds, superClassesList); + + final OAutoshardedStorage autoshardedStorage = (OAutoshardedStorage) storage; + OCommandSQL commandSQL = new OCommandSQL(cmd.toString()); + commandSQL.addExcludedNode(autoshardedStorage.getNodeId()); + + final Object res = db.command(commandSQL).execute(); + + } else if (storage instanceof OStorageProxy) { + db.command(new OCommandSQL(cmd.toString())).execute(); + reload(); + } else + createClassInternal(className, clusterIds, superClassesList); + + result = classes.get(className.toLowerCase(Locale.ENGLISH)); + + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onCreateClass(getDatabase(), result); + + } finally { + releaseSchemaWriteLock(); + } + + return result; + } + + private OClass doCreateClass(final String className, final int clusters, final int retry, OClass... superClasses) { + OClass result; + + final ODatabaseDocumentInternal db = getDatabase(); + final OStorage storage = db.getStorage(); + StringBuilder cmd = null; + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_CREATE); + if (superClasses != null) + OClassImpl.checkParametersConflict(Arrays.asList(superClasses)); + acquireSchemaWriteLock(); + try { + + final String key = className.toLowerCase(Locale.ENGLISH); + if (classes.containsKey(key) && retry == 0) + throw new OSchemaException("Class '" + className + "' already exists in current database"); + + cmd = new StringBuilder("create class "); + // if (getDatabase().getStorage().getConfiguration().isStrictSql()) + // cmd.append('`'); + cmd.append(className); + // if (getDatabase().getStorage().getConfiguration().isStrictSql()) + // cmd.append('`'); + + List superClassesList = new ArrayList(); + if (superClasses != null && superClasses.length > 0) { + boolean first = true; + for (OClass superClass : superClasses) { + // Filtering for null + if (superClass != null) { + if (first) + cmd.append(" extends "); + else + cmd.append(", "); + cmd.append(superClass.getName()); + first = false; + superClassesList.add(superClass); + } + } + } + + if (clusters == 0) + cmd.append(" abstract"); + else { + cmd.append(" clusters "); + cmd.append(clusters); + } + + if (executeThroughDistributedStorage()) { + + final int[] clusterIds = createClusters(className, clusters); + createClassInternal(className, clusterIds, superClassesList); + + final OAutoshardedStorage autoshardedStorage = (OAutoshardedStorage) storage; + OCommandSQL commandSQL = new OCommandSQL(cmd.toString()); + commandSQL.addExcludedNode(autoshardedStorage.getNodeId()); + + final Object res = db.command(commandSQL).execute(); + + } else if (storage instanceof OStorageProxy) { + db.command(new OCommandSQL(cmd.toString())).execute(); + reload(); + } else { + final int[] clusterIds = createClusters(className, clusters); + createClassInternal(className, clusterIds, superClassesList); + } + + result = classes.get(className.toLowerCase(Locale.ENGLISH)); + + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onCreateClass(getDatabase(), result); + + } catch (ClusterIdsAreEmptyException e) { + throw OException.wrapException(new OSchemaException("Cannot create class '" + className + "'"), e); + } finally { + releaseSchemaWriteLock(); + } + + return result; + } + + private boolean executeThroughDistributedStorage() { + return getDatabase().getStorage() instanceof OAutoshardedStorage && !OScenarioThreadLocal.INSTANCE.isRunModeDistributed(); + } + + private OClass createClassInternal(final String className, final int[] clusterIdsToAdd, final List superClasses) + throws ClusterIdsAreEmptyException { + acquireSchemaWriteLock(); + try { + if (className == null || className.length() == 0) + throw new OSchemaException("Found class name null or empty"); + + if (Character.isDigit(className.charAt(0))) + throw new OSchemaException("Found invalid class name. Cannot start with numbers"); + + final Character wrongCharacter = checkClassNameIfValid(className); + if (wrongCharacter != null) + throw new OSchemaException("Found invalid class name. Character '" + wrongCharacter + "' cannot be used in class name."); + + final ODatabaseDocumentInternal database = getDatabase(); + final OStorage storage = database.getStorage(); + checkEmbedded(storage); + + checkClustersAreAbsent(clusterIdsToAdd); + + final int[] clusterIds; + if (clusterIdsToAdd == null || clusterIdsToAdd.length == 0) { + throw new ClusterIdsAreEmptyException(); + + } else + clusterIds = clusterIdsToAdd; + + database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_CREATE); + + final String key = className.toLowerCase(Locale.ENGLISH); + + if (classes.containsKey(key)) + throw new OSchemaException("Class '" + className + "' already exists in current database"); + + OClassImpl cls = new OClassImpl(this, className, clusterIds); + + classes.put(key, cls); + + if (superClasses != null && superClasses.size() > 0) { + cls.setSuperClassesInternal(superClasses); + for (OClass superClass : superClasses) { + // UPDATE INDEXES + final int[] clustersToIndex = superClass.getPolymorphicClusterIds(); + final String[] clusterNames = new String[clustersToIndex.length]; + for (int i = 0; i < clustersToIndex.length; i++) + clusterNames[i] = database.getClusterNameById(clustersToIndex[i]); + + for (OIndex index : superClass.getIndexes()) + for (String clusterName : clusterNames) + if (clusterName != null) + database.getMetadata().getIndexManager().addClusterToIndex(clusterName, index.getName()); + } + } + + addClusterClassMap(cls); + + return cls; + } finally { + releaseSchemaWriteLock(); + } + } + + private int[] createClusters(final String iClassName) { + return createClusters(iClassName, getDatabase().getStorage().getConfiguration().getMinimumClusters()); + } + + private int[] createClusters(String className, int minimumClusters) { + className = className.toLowerCase(Locale.ENGLISH); + + final ODatabaseDocumentInternal database = getDatabase(); + + int[] clusterIds; + + if (internalClasses.contains(className.toLowerCase(Locale.ENGLISH))) { + // INTERNAL CLASS, SET TO 1 + minimumClusters = 1; + } + + clusterIds = new int[minimumClusters]; + clusterIds[0] = database.getClusterIdByName(className); + if (clusterIds[0] > -1) { + // CHECK THE CLUSTER HAS NOT BEEN ALREADY ASSIGNED + final OClass cls = clustersToClasses.get(clusterIds[0]); + if (cls != null) + clusterIds[0] = database.addCluster(getNextAvailableClusterName(className)); + } else + // JUST KEEP THE CLASS NAME. THIS IS FOR LEGACY REASONS + clusterIds[0] = database.addCluster(className); + + for (int i = 1; i < minimumClusters; ++i) + clusterIds[i] = database.addCluster(getNextAvailableClusterName(className)); + + return clusterIds; + } + + private String getNextAvailableClusterName(final String className) { + for (int i = 1; ; ++i) { + final String clusterName = className + "_" + i; + if (getDatabase().getClusterIdByName(clusterName) < 0) + // FREE NAME + return clusterName; + } + } + + private void dropClassInternal(final String className) { + acquireSchemaWriteLock(); + try { + if (getDatabase().getTransaction().isActive()) + throw new IllegalStateException("Cannot drop a class inside a transaction"); + + if (className == null) + throw new IllegalArgumentException("Class name is null"); + + getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE); + + final String key = className.toLowerCase(Locale.ENGLISH); + + final OClass cls = classes.get(key); + if (cls == null) + throw new OSchemaException("Class '" + className + "' was not found in current database"); + + if (!cls.getSubclasses().isEmpty()) + throw new OSchemaException("Class '" + className + "' cannot be dropped because it has sub classes " + cls.getSubclasses() + + ". Remove the dependencies before trying to drop it again"); + + checkEmbedded(getDatabase().getStorage()); + + for (OClass superClass : cls.getSuperClasses()) { + // REMOVE DEPENDENCY FROM SUPERCLASS + ((OClassImpl) superClass).removeBaseClassInternal(cls); + } + for (int id : cls.getClusterIds()) { + if (id != -1) + deleteCluster(getDatabase(), id); + } + + dropClassIndexes(cls); + + classes.remove(key); + + if (cls.getShortName() != null) + // REMOVE THE ALIAS TOO + classes.remove(cls.getShortName().toLowerCase(Locale.ENGLISH)); + + removeClusterClassMap(cls); + + // WAKE UP DB LIFECYCLE LISTENER + for (Iterator it = Orient.instance().getDbLifecycleListeners(); it.hasNext(); ) + it.next().onDropClass(getDatabase(), cls); + + } finally { + releaseSchemaWriteLock(); + } + } + + private void deleteCluster(final ODatabaseDocumentInternal db, final int clusterId) { + db.getStorage().dropCluster(clusterId, false); + db.getLocalCache().freeCluster(clusterId); + } + + private void saveInternal() { + final ODatabaseDocument db = getDatabase(); + + if (db.getTransaction().isActive()) { + reload(null, true); + throw new OSchemaException("Cannot change the schema while a transaction is active. Schema changes are not transactional"); + } + + setDirty(); + + OScenarioThreadLocal.executeAsDistributed(new Callable() { + @Override + public Object call() { + try { + toStream(); + document.save(OMetadataDefault.CLUSTER_INTERNAL_NAME); + } catch (OConcurrentModificationException e) { + reload(null, true); + throw e; + } + return null; + } + }); + + snapshot = new OImmutableSchema(this); + } + + private void addClusterClassMap(final OClass cls) { + if (!clustersCanNotBeSharedAmongClasses) + return; + + for (int clusterId : cls.getClusterIds()) { + if (clusterId < 0) + continue; + + clustersToClasses.put(clusterId, cls); + } + + } + + private void removeClusterClassMap(final OClass cls) { + if (!clustersCanNotBeSharedAmongClasses) + return; + + for (int clusterId : cls.getClusterIds()) { + if (clusterId < 0) + continue; + + clustersToClasses.remove(clusterId); + } + + } + + private void checkClustersAreAbsent(final int[] iClusterIds) { + if (!clustersCanNotBeSharedAmongClasses || iClusterIds == null) + return; + + for (int clusterId : iClusterIds) { + if (clusterId < 0) + continue; + + if (clustersToClasses.containsKey(clusterId)) + throw new OSchemaException( + "Cluster with id " + clusterId + " already belongs to class " + clustersToClasses.get(clusterId)); + } + } + + private void dropClassIndexes(final OClass cls) { + final ODatabaseDocument database = getDatabase(); + final OIndexManager indexManager = database.getMetadata().getIndexManager(); + + for (final OIndex index : indexManager.getClassIndexes(cls.getName())) + indexManager.dropIndex(index.getName()); + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + private void ensurePropertiesSize(int size) { + while (properties.size() <= size) + properties.add(null); + } + + private static class OModificationsCounter extends ThreadLocal { + @Override + protected OModifiableInteger initialValue() { + return new OModifiableInteger(0); + } + } + + public int addBlobCluster(int clusterId) { + acquireSchemaWriteLock(); + try { + checkClusterCanBeAdded(clusterId, null); + blobClusters.add(clusterId); + } finally { + releaseSchemaWriteLock(); + } + return clusterId; + } + + public void removeBlobCluster(String clusterName) { + acquireSchemaWriteLock(); + try { + int clusterId = getClusterId(clusterName); + blobClusters.remove(clusterId); + } finally { + releaseSchemaWriteLock(); + } + } + + protected int getClusterId(final String stringValue) { + int clId; + try { + clId = Integer.parseInt(stringValue); + } catch (NumberFormatException e) { + clId = getDatabase().getClusterIdByName(stringValue); + } + return clId; + } + + protected int createClusterIfNeeded(String nameOrId) { + final String[] parts = nameOrId.split(" "); + int clId = getClusterId(parts[0]); + + if (clId == NOT_EXISTENT_CLUSTER_ID) { + try { + clId = Integer.parseInt(parts[0]); + throw new IllegalArgumentException("Cluster id '" + clId + "' cannot be added"); + } catch (NumberFormatException e) { + clId = getDatabase().addCluster(parts[0]); + } + } + + return clId; + } + + public Set getBlobClusters() { + return Collections.unmodifiableSet(blobClusters); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OType.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OType.java new file mode 100755 index 00000000000..02b470a5533 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/OType.java @@ -0,0 +1,751 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.schema; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.types.OBinary; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.ParseException; +import java.util.*; + +/** + * Generic representation of a type.
          + * allowAssignmentFrom accepts any class, but Array.class means that the type accepts generic Arrays. + * + * @author Luca Garulli + */ +public enum OType { + BOOLEAN("Boolean", 0, Boolean.class, new Class[] { Number.class }), + + INTEGER("Integer", 1, Integer.class, new Class[] { Number.class }), + + SHORT("Short", 2, Short.class, new Class[] { Number.class }), + + LONG("Long", 3, Long.class, new Class[] { Number.class, }), + + FLOAT("Float", 4, Float.class, new Class[] { Number.class }), + + DOUBLE("Double", 5, Double.class, new Class[] { Number.class }), + + DATETIME("Datetime", 6, Date.class, new Class[] { Date.class, Number.class }), + + STRING("String", 7, String.class, new Class[] { Enum.class }), + + BINARY("Binary", 8, byte[].class, new Class[] { byte[].class }), + + EMBEDDED("Embedded", 9, Object.class, new Class[] { ODocumentSerializable.class, OSerializableStream.class }), + + EMBEDDEDLIST("EmbeddedList", 10, List.class, new Class[] { List.class, OMultiCollectionIterator.class }), + + EMBEDDEDSET("EmbeddedSet", 11, Set.class, new Class[] { Set.class }), + + EMBEDDEDMAP("EmbeddedMap", 12, Map.class, new Class[] { Map.class }), + + LINK("Link", 13, OIdentifiable.class, new Class[] { OIdentifiable.class, ORID.class }), + + LINKLIST("LinkList", 14, List.class, new Class[] { List.class }), + + LINKSET("LinkSet", 15, Set.class, new Class[] { Set.class }), + + LINKMAP("LinkMap", 16, Map.class, new Class[] { Map.class }), + + BYTE("Byte", 17, Byte.class, new Class[] { Number.class }), + + TRANSIENT("Transient", 18, null, new Class[] {}), + + DATE("Date", 19, Date.class, new Class[] { Number.class }), + + CUSTOM("Custom", 20, OSerializableStream.class, new Class[] { OSerializableStream.class, Serializable.class }), + + DECIMAL("Decimal", 21, BigDecimal.class, new Class[] { BigDecimal.class, Number.class }), + + LINKBAG("LinkBag", 22, ORidBag.class, new Class[] { ORidBag.class }), + + ANY("Any", 23, null, new Class[] {}); + + // Don't change the order, the type discover get broken if you change the order. + protected static final OType[] TYPES = new OType[] { EMBEDDEDLIST, EMBEDDEDSET, EMBEDDEDMAP, LINK, CUSTOM, EMBEDDED, STRING, + DATETIME }; + + protected static final OType[] TYPES_BY_ID = new OType[24]; + // Values previosly stored in javaTypes + protected static final Map, OType> TYPES_BY_CLASS = new HashMap, OType>(); + + static { + for (OType oType : values()) { + TYPES_BY_ID[oType.id] = oType; + } + // This is made by hand because not all types should be add. + TYPES_BY_CLASS.put(Boolean.class, BOOLEAN); + TYPES_BY_CLASS.put(Boolean.TYPE, BOOLEAN); + TYPES_BY_CLASS.put(Integer.TYPE, INTEGER); + TYPES_BY_CLASS.put(Integer.class, INTEGER); + TYPES_BY_CLASS.put(BigInteger.class, INTEGER); + TYPES_BY_CLASS.put(Short.class, SHORT); + TYPES_BY_CLASS.put(Short.TYPE, SHORT); + TYPES_BY_CLASS.put(Long.class, LONG); + TYPES_BY_CLASS.put(Long.TYPE, LONG); + TYPES_BY_CLASS.put(Float.TYPE, FLOAT); + TYPES_BY_CLASS.put(Float.class, FLOAT); + TYPES_BY_CLASS.put(Double.TYPE, DOUBLE); + TYPES_BY_CLASS.put(Double.class, DOUBLE); + TYPES_BY_CLASS.put(Date.class, DATETIME); + TYPES_BY_CLASS.put(String.class, STRING); + TYPES_BY_CLASS.put(Enum.class, STRING); + TYPES_BY_CLASS.put(byte[].class, BINARY); + TYPES_BY_CLASS.put(Byte.class, BYTE); + TYPES_BY_CLASS.put(Byte.TYPE, BYTE); + TYPES_BY_CLASS.put(Character.class, STRING); + TYPES_BY_CLASS.put(Character.TYPE, STRING); + TYPES_BY_CLASS.put(ORecordId.class, LINK); + TYPES_BY_CLASS.put(BigDecimal.class, DECIMAL); + TYPES_BY_CLASS.put(ORidBag.class, LINKBAG); + TYPES_BY_CLASS.put(OTrackedSet.class, EMBEDDEDSET); + TYPES_BY_CLASS.put(ORecordLazySet.class, LINKSET); + TYPES_BY_CLASS.put(OTrackedList.class, EMBEDDEDLIST); + TYPES_BY_CLASS.put(ORecordLazyList.class, LINKLIST); + TYPES_BY_CLASS.put(OTrackedMap.class, EMBEDDEDMAP); + TYPES_BY_CLASS.put(ORecordLazyMap.class, LINKMAP); + BYTE.castable.add(BOOLEAN); + SHORT.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE })); + INTEGER.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE, SHORT })); + LONG.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE, SHORT, INTEGER })); + FLOAT.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE, SHORT, INTEGER })); + DOUBLE.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE, SHORT, INTEGER, LONG, FLOAT })); + DECIMAL.castable.addAll(Arrays.asList(new OType[] { BOOLEAN, BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE })); + LINKLIST.castable.add(LINKSET); + EMBEDDEDLIST.castable.add(EMBEDDEDSET); + } + + protected final String name; + protected final int id; + protected final Class javaDefaultType; + protected final Class[] allowAssignmentFrom; + protected final Set castable; + + private OType(final String iName, final int iId, final Class iJavaDefaultType, final Class[] iAllowAssignmentBy) { + name = iName; + id = iId; + javaDefaultType = iJavaDefaultType; + allowAssignmentFrom = iAllowAssignmentBy; + castable = new HashSet(); + castable.add(this); + } + + /** + * Return the type by ID. + * + * @param iId The id to search + * @return The type if any, otherwise null + */ + public static OType getById(final byte iId) { + if (iId >= 0 && iId < TYPES_BY_ID.length) + return TYPES_BY_ID[iId]; + return null; + } + + /** + * Get the identifier of the type. use this instead of {@link Enum#ordinal()} for guarantee a cross code version identifier. + * + * @return the identifier of the type. + */ + public int getId() { + return id; + } + + /** + * Return the correspondent type by checking the "assignability" of the class received as parameter. + * + * @param iClass Class to check + * @return OType instance if found, otherwise null + */ + public static OType getTypeByClass(final Class iClass) { + if (iClass == null) + return null; + + OType type = TYPES_BY_CLASS.get(iClass); + if (type != null) + return type; + type = getTypeByClassInherit(iClass); + + return type; + } + + private static OType getTypeByClassInherit(final Class iClass) { + if (iClass.isArray()) + return EMBEDDEDLIST; + int priority = 0; + boolean comparedAtLeastOnce; + do { + comparedAtLeastOnce = false; + for (final OType type : TYPES) { + if (type.allowAssignmentFrom.length > priority) { + if (type.allowAssignmentFrom[priority].isAssignableFrom(iClass)) + return type; + comparedAtLeastOnce = true; + } + } + + priority++; + } while (comparedAtLeastOnce); + return null; + } + + public static OType getTypeByValue(Object value) { + if (value == null) + return null; + Class clazz = value.getClass(); + OType type = TYPES_BY_CLASS.get(clazz); + if (type != null) + return type; + + OType byType = getTypeByClassInherit(clazz); + if (LINK == byType) { + if (value instanceof ODocument && ((ODocument) value).hasOwners()) + return EMBEDDED; + } else if (EMBEDDEDSET == byType) { + if (checkLinkCollection(((Collection) value))) + return LINKSET; + } else if (EMBEDDEDLIST == byType && !clazz.isArray()) { + if (value instanceof OMultiCollectionIterator) + type = ((OMultiCollectionIterator) value).isEmbedded() ? OType.EMBEDDEDLIST : OType.LINKLIST; + else if (checkLinkCollection(((Collection) value))) + return LINKLIST; + + } else if (EMBEDDEDMAP == byType) { + if (checkLinkCollection(((Map) value).values())) + return LINKMAP; + } + return byType; + } + + private static boolean checkLinkCollection(Collection toCheck) { + boolean empty = true; + for (Object object : toCheck) { + if (object != null && !(object instanceof OIdentifiable)) + return false; + else if (object != null) + empty = false; + } + if (!empty) + return true; + return false; + } + + public static boolean isSimpleType(final Object iObject) { + if (iObject == null) + return false; + + final Class iType = iObject.getClass(); + + if (iType.isPrimitive() || Number.class.isAssignableFrom(iType) || String.class.isAssignableFrom(iType) || Boolean.class + .isAssignableFrom(iType) || Date.class.isAssignableFrom(iType) || (iType.isArray() && (iType.equals(byte[].class) || iType + .equals(char[].class) || iType.equals(int[].class) || iType.equals(long[].class) || iType.equals(double[].class) || iType + .equals(float[].class) || iType.equals(short[].class) || iType.equals(Integer[].class) || iType.equals(String[].class) + || iType.equals(Long[].class) || iType.equals(Short[].class) || iType.equals(Double[].class)))) + return true; + + return false; + } + + /** + * Convert types based on the iTargetClass parameter. + * + * @param iValue Value to convert + * @param iTargetClass Expected class + * @return The converted value or the original if no conversion was applied + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) public static Object convert(final Object iValue, final Class iTargetClass) { + if (iValue == null) + return null; + + if (iTargetClass == null) + return iValue; + + if (iValue.getClass().equals(iTargetClass)) + // SAME TYPE: DON'T CONVERT IT + return iValue; + + if (iTargetClass.isAssignableFrom(iValue.getClass())) + // COMPATIBLE TYPES: DON'T CONVERT IT + return iValue; + + try { + if (iValue instanceof OBinary && iTargetClass.isAssignableFrom(byte[].class)) + return ((OBinary) iValue).toByteArray(); + else if (byte[].class.isAssignableFrom(iTargetClass)) { + return OStringSerializerHelper.getBinaryContent(iValue); + } else if (byte[].class.isAssignableFrom(iValue.getClass())) { + return iValue; + } else if (iTargetClass.isEnum()) { + if (iValue instanceof Number) + return ((Class) iTargetClass).getEnumConstants()[((Number) iValue).intValue()]; + return Enum.valueOf((Class) iTargetClass, iValue.toString()); + } else if (iTargetClass.equals(Byte.TYPE) || iTargetClass.equals(Byte.class)) { + if (iValue instanceof Byte) + return iValue; + else if (iValue instanceof String) + return Byte.parseByte((String) iValue); + else + return ((Number) iValue).byteValue(); + + } else if (iTargetClass.equals(Short.TYPE) || iTargetClass.equals(Short.class)) { + if (iValue instanceof Short) + return iValue; + else if (iValue instanceof String) + return Short.parseShort((String) iValue); + else + return ((Number) iValue).shortValue(); + + } else if (iTargetClass.equals(Integer.TYPE) || iTargetClass.equals(Integer.class)) { + if (iValue instanceof Integer) + return iValue; + else if (iValue instanceof String) + return Integer.parseInt((String) iValue); + else + return ((Number) iValue).intValue(); + + } else if (iTargetClass.equals(Long.TYPE) || iTargetClass.equals(Long.class)) { + if (iValue instanceof Long) + return iValue; + else if (iValue instanceof String) + return Long.parseLong((String) iValue); + else if (iValue instanceof Date) + return ((Date) iValue).getTime(); + else + return ((Number) iValue).longValue(); + + } else if (iTargetClass.equals(Float.TYPE) || iTargetClass.equals(Float.class)) { + if (iValue instanceof Float) + return iValue; + else if (iValue instanceof String) + return Float.parseFloat((String) iValue); + else + return ((Number) iValue).floatValue(); + + } else if (iTargetClass.equals(BigDecimal.class)) { + if (iValue instanceof String) + return new BigDecimal((String) iValue); + else if (iValue instanceof Number) + return new BigDecimal(iValue.toString()); + + } else if (iTargetClass.equals(Double.TYPE) || iTargetClass.equals(Double.class)) { + if (iValue instanceof Double) + return iValue; + else if (iValue instanceof String) + return Double.parseDouble((String) iValue); + else if (iValue instanceof Float) + // THIS IS NECESSARY DUE TO A BUG/STRANGE BEHAVIOR OF JAVA BY LOSSING PRECISION + return Double.parseDouble((String) iValue.toString()); + else + return ((Number) iValue).doubleValue(); + + } else if (iTargetClass.equals(Boolean.TYPE) || iTargetClass.equals(Boolean.class)) { + if (iValue instanceof Boolean) + return ((Boolean) iValue).booleanValue(); + else if (iValue instanceof String) { + if (((String) iValue).equalsIgnoreCase("true")) + return Boolean.TRUE; + else if (((String) iValue).equalsIgnoreCase("false")) + return Boolean.FALSE; + throw new IllegalArgumentException("Value is not boolean. Expected true or false but received '" + iValue + "'"); + } else if (iValue instanceof Number) + return ((Number) iValue).intValue() != 0; + + } else if (Set.class.isAssignableFrom(iTargetClass)) { + // The caller specifically wants a Set. If the value is a collection + // we will add all of the items in the collection to a set. Otherwise + // we will create a singleton set with only the value in it. + if (iValue instanceof Collection) { + final Set set = new HashSet(); + set.addAll((Collection) iValue); + return set; + } else { + return Collections.singleton(iValue); + } + + } else if (List.class.isAssignableFrom(iTargetClass)) { + // The caller specifically wants a List. If the value is a collection + // we will add all of the items in the collection to a List. Otherwise + // we will create a singleton List with only the value in it. + if (iValue instanceof Collection) { + final List list = new ArrayList(); + list.addAll((Collection) iValue); + return list; + } else { + return Collections.singletonList(iValue); + } + + } else if (Collection.class.equals(iTargetClass)) { + // The caller specifically wants a Collection of any type. + // we will return a list if the value is a collection or + // a singleton set if the value is not a collection. + if (iValue instanceof Collection) { + final List set = new ArrayList(); + set.addAll((Collection) iValue); + return set; + } else { + return Collections.singleton(iValue); + } + + } else if (iTargetClass.equals(Date.class)) { + if (iValue instanceof Number) + return new Date(((Number) iValue).longValue()); + if (iValue instanceof String) { + if (OIOUtils.isLong(iValue.toString())) + return new Date(Long.parseLong(iValue.toString())); + try { + return ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateTimeFormatInstance() + .parse((String) iValue); + } catch (ParseException e) { + return ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateFormatInstance() + .parse((String) iValue); + } + } + } else if (iTargetClass.equals(String.class)) { + return iValue.toString(); + } else if (iTargetClass.equals(OIdentifiable.class)) { + if (OMultiValue.isMultiValue(iValue)) { + List result = new ArrayList(); + for (Object o : OMultiValue.getMultiValueIterable(iValue)) { + if (o instanceof OIdentifiable) { + result.add((OIdentifiable) o); + } else if (o instanceof String) { + try { + result.add(new ORecordId((String) iValue)); + } catch (Exception e) { + OLogManager.instance().debug(OType.class, "Error in conversion of value '%s' to type '%s'", iValue, iTargetClass); + } + } + } + return result; + } else if (iValue instanceof String) { + try { + return new ORecordId((String) iValue); + } catch (Exception e) { + OLogManager.instance().debug(OType.class, "Error in conversion of value '%s' to type '%s'", iValue, iTargetClass); + } + } + } + } catch (IllegalArgumentException e) { + // PASS THROUGH + throw e; + } catch (Exception e) { + OLogManager.instance().debug(OType.class, "Error in conversion of value '%s' to type '%s'", iValue, iTargetClass); + return null; + } + + return iValue; + } + + public static Number increment(final Number a, final Number b) { + if (a == null || b == null) + throw new IllegalArgumentException("Cannot increment a null value"); + + if (a instanceof Integer) { + if (b instanceof Integer) { + final int sum = a.intValue() + b.intValue(); + if (sum < 0 && a.intValue() > 0 && b.intValue() > 0) + // SPECIAL CASE: UPGRADE TO LONG + return new Long(a.intValue() + b.intValue()); + return sum; + } else if (b instanceof Long) + return new Long(a.intValue() + b.longValue()); + else if (b instanceof Short) { + final int sum = a.intValue() + b.shortValue(); + if (sum < 0 && a.intValue() > 0 && b.shortValue() > 0) + // SPECIAL CASE: UPGRADE TO LONG + return new Long(a.intValue() + b.shortValue()); + return sum; + } else if (b instanceof Float) + return new Float(a.intValue() + b.floatValue()); + else if (b instanceof Double) + return new Double(a.intValue() + b.doubleValue()); + else if (b instanceof BigDecimal) + return new BigDecimal(a.intValue()).add((BigDecimal) b); + + } else if (a instanceof Long) { + if (b instanceof Integer) + return new Long(a.longValue() + b.intValue()); + else if (b instanceof Long) + return new Long(a.longValue() + b.longValue()); + else if (b instanceof Short) + return new Long(a.longValue() + b.shortValue()); + else if (b instanceof Float) + return new Float(a.longValue() + b.floatValue()); + else if (b instanceof Double) + return new Double(a.longValue() + b.doubleValue()); + else if (b instanceof BigDecimal) + return new BigDecimal(a.longValue()).add((BigDecimal) b); + + } else if (a instanceof Short) { + if (b instanceof Integer) { + final int sum = a.shortValue() + b.intValue(); + if (sum < 0 && a.shortValue() > 0 && b.intValue() > 0) + // SPECIAL CASE: UPGRADE TO LONG + return new Long(a.shortValue() + b.intValue()); + return sum; + } else if (b instanceof Long) + return new Long(a.shortValue() + b.longValue()); + else if (b instanceof Short) { + final int sum = a.shortValue() + b.shortValue(); + if (sum < 0 && a.shortValue() > 0 && b.shortValue() > 0) + // SPECIAL CASE: UPGRADE TO INTEGER + return new Integer(a.intValue() + b.intValue()); + return sum; + } else if (b instanceof Float) + return new Float(a.shortValue() + b.floatValue()); + else if (b instanceof Double) + return new Double(a.shortValue() + b.doubleValue()); + else if (b instanceof BigDecimal) + return new BigDecimal(a.shortValue()).add((BigDecimal) b); + + } else if (a instanceof Float) { + if (b instanceof Integer) + return new Float(a.floatValue() + b.intValue()); + else if (b instanceof Long) + return new Float(a.floatValue() + b.longValue()); + else if (b instanceof Short) + return new Float(a.floatValue() + b.shortValue()); + else if (b instanceof Float) + return new Float(a.floatValue() + b.floatValue()); + else if (b instanceof Double) + return new Double(a.floatValue() + b.doubleValue()); + else if (b instanceof BigDecimal) + return new BigDecimal(a.floatValue()).add((BigDecimal) b); + + } else if (a instanceof Double) { + if (b instanceof Integer) + return new Double(a.doubleValue() + b.intValue()); + else if (b instanceof Long) + return new Double(a.doubleValue() + b.longValue()); + else if (b instanceof Short) + return new Double(a.doubleValue() + b.shortValue()); + else if (b instanceof Float) + return new Double(a.doubleValue() + b.floatValue()); + else if (b instanceof Double) + return new Double(a.doubleValue() + b.doubleValue()); + else if (b instanceof BigDecimal) + return new BigDecimal(a.doubleValue()).add((BigDecimal) b); + + } else if (a instanceof BigDecimal) { + if (b instanceof Integer) + return ((BigDecimal) a).add(new BigDecimal(b.intValue())); + else if (b instanceof Long) + return ((BigDecimal) a).add(new BigDecimal(b.longValue())); + else if (b instanceof Short) + return ((BigDecimal) a).add(new BigDecimal(b.shortValue())); + else if (b instanceof Float) + return ((BigDecimal) a).add(new BigDecimal(b.floatValue())); + else if (b instanceof Double) + return ((BigDecimal) a).add(new BigDecimal(b.doubleValue())); + else if (b instanceof BigDecimal) + return ((BigDecimal) a).add((BigDecimal) b); + + } + + throw new IllegalArgumentException( + "Cannot increment value '" + a + "' (" + a.getClass() + ") with '" + b + "' (" + b.getClass() + ")"); + } + + public static Number[] castComparableNumber(Number context, Number max) { + // CHECK FOR CONVERSION + if (context instanceof Integer) { + // SHORT + if (max instanceof Integer) + context = context.intValue(); + else if (max instanceof Long) + context = context.longValue(); + else if (max instanceof Float) + context = context.floatValue(); + else if (max instanceof Double) + context = context.doubleValue(); + else if (max instanceof BigDecimal) + context = new BigDecimal(context.intValue()); + + } else if (context instanceof Integer) { + // INTEGER + if (max instanceof Long) + context = context.longValue(); + else if (max instanceof Float) + context = context.floatValue(); + else if (max instanceof Double) + context = context.doubleValue(); + else if (max instanceof BigDecimal) + context = new BigDecimal(context.intValue()); + else if (max instanceof Short) + max = max.intValue(); + + } else if (context instanceof Long) { + // LONG + if (max instanceof Float) + context = context.floatValue(); + else if (max instanceof Double) + context = context.doubleValue(); + else if (max instanceof BigDecimal) + context = new BigDecimal(context.longValue()); + else if (max instanceof Integer || max instanceof Short) + max = max.longValue(); + + } else if (context instanceof Float) { + // FLOAT + if (max instanceof Double) + context = context.doubleValue(); + else if (max instanceof BigDecimal) + context = new BigDecimal(context.floatValue()); + else if (max instanceof Short || max instanceof Integer || max instanceof Long) + max = max.floatValue(); + + } else if (context instanceof Double) { + // DOUBLE + if (max instanceof BigDecimal) + context = new BigDecimal(context.doubleValue()); + else if (max instanceof Short || max instanceof Integer || max instanceof Long || max instanceof Float) + max = max.doubleValue(); + + } else if (context instanceof BigDecimal) { + // DOUBLE + if (max instanceof Integer) + max = new BigDecimal((Integer) max); + else if (max instanceof Float) + max = new BigDecimal((Float) max); + else if (max instanceof Double) + max = new BigDecimal((Double) max); + else if (max instanceof Short) + max = new BigDecimal((Short) max); + } + + return new Number[] { context, max }; + } + + /** + * Convert the input object to an integer. + * + * @param iValue Any type supported + * @return The integer value if the conversion succeed, otherwise the IllegalArgumentException exception + */ + public int asInt(final Object iValue) { + if (iValue instanceof Number) + return ((Number) iValue).intValue(); + else if (iValue instanceof String) + return Integer.valueOf((String) iValue); + else if (iValue instanceof Boolean) + return ((Boolean) iValue) ? 1 : 0; + + throw new IllegalArgumentException("Cannot convert value " + iValue + " to int for type: " + name); + } + + /** + * Convert the input object to a long. + * + * @param iValue Any type supported + * @return The long value if the conversion succeed, otherwise the IllegalArgumentException exception + */ + public long asLong(final Object iValue) { + if (iValue instanceof Number) + return ((Number) iValue).longValue(); + else if (iValue instanceof String) + return Long.valueOf((String) iValue); + else if (iValue instanceof Boolean) + return ((Boolean) iValue) ? 1 : 0; + + throw new IllegalArgumentException("Cannot convert value " + iValue + " to long for type: " + name); + } + + /** + * Convert the input object to a float. + * + * @param iValue Any type supported + * @return The float value if the conversion succeed, otherwise the IllegalArgumentException exception + */ + public float asFloat(final Object iValue) { + if (iValue instanceof Number) + return ((Number) iValue).floatValue(); + else if (iValue instanceof String) + return Float.valueOf((String) iValue); + + throw new IllegalArgumentException("Cannot convert value " + iValue + " to float for type: " + name); + } + + /** + * Convert the input object to a double. + * + * @param iValue Any type supported + * @return The double value if the conversion succeed, otherwise the IllegalArgumentException exception + */ + public double asDouble(final Object iValue) { + if (iValue instanceof Number) + return ((Number) iValue).doubleValue(); + else if (iValue instanceof String) + return Double.valueOf((String) iValue); + + throw new IllegalArgumentException("Cannot convert value " + iValue + " to double for type: " + name); + } + + /** + * Convert the input object to a string. + * + * @param iValue Any type supported + * @return The string if the conversion succeed, otherwise the IllegalArgumentException exception + */ + @Deprecated public String asString(final Object iValue) { + return iValue.toString(); + } + + public boolean isMultiValue() { + return this == EMBEDDEDLIST || this == EMBEDDEDMAP || this == EMBEDDEDSET || this == LINKLIST || this == LINKMAP + || this == LINKSET || this == LINKBAG; + } + + public boolean isLink() { + return this == LINK || this == LINKSET || this == LINKLIST || this == LINKMAP || this == LINKBAG; + } + + public boolean isEmbedded() { + return this == EMBEDDED || this == EMBEDDEDLIST || this == EMBEDDEDMAP || this == EMBEDDEDSET; + } + + public Class getDefaultJavaType() { + return javaDefaultType; + } + + public Set getCastable() { + return castable; + } + + @Deprecated public Class[] getJavaTypes() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OBalancedClusterSelectionStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OBalancedClusterSelectionStrategy.java new file mode 100644 index 00000000000..5332c0eea13 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OBalancedClusterSelectionStrategy.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema.clusterselection; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Returns the cluster selecting the most empty between all configured clusters. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OBalancedClusterSelectionStrategy implements OClusterSelectionStrategy { + public static final String NAME = "balanced"; + protected static final long REFRESH_TIMEOUT = 5000; + protected long lastCount = -1; + protected int smallerClusterId = -1; + + public int getCluster(final OClass iClass, final ODocument doc) { + final int[] clusters = iClass.getClusterIds(); + if (clusters.length == 1) + // ONLY ONE: RETURN THE FIRST ONE + return clusters[0]; + + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db == null) + return clusters[0]; + + if (lastCount < 0 || System.currentTimeMillis() - lastCount > REFRESH_TIMEOUT) { + // REFRESH COUNTERS + long min = Long.MAX_VALUE; + + for (int cluster : clusters) { + final long count = db.countClusterElements(cluster); + if (count < min) { + min = count; + smallerClusterId = cluster; + } + } + lastCount = System.currentTimeMillis(); + } + + return smallerClusterId; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionFactory.java new file mode 100644 index 00000000000..c8d1ded60f1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema.clusterselection; + +import static com.orientechnologies.common.util.OClassLoaderHelper.lookupProviderWithOrientClassLoader; + +import java.lang.reflect.Method; +import java.util.Iterator; + +import com.orientechnologies.common.factory.OConfigurableStatefulFactory; +import com.orientechnologies.common.log.OLogManager; + +/** + * Factory to get the cluster selection strategy. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OClusterSelectionFactory extends OConfigurableStatefulFactory { + public OClusterSelectionFactory() { + setDefaultClass(ORoundRobinClusterSelectionStrategy.class); + this.registerStrategy(); + } + + private static ClassLoader orientClassLoader = OClusterSelectionFactory.class.getClassLoader(); + private void registerStrategy() { + final Iterator ite = lookupProviderWithOrientClassLoader(OClusterSelectionStrategy.class, orientClassLoader); + while(ite.hasNext()) { + OClusterSelectionStrategy strategy = ite.next(); + Class clz = strategy.getClass(); + try { + Method method = clz.getMethod("getName"); + if(method != null) { + String key = (String)method.invoke(clz.newInstance()); + register(key, clz); + } else + OLogManager.instance().error(this, "getName() funciton missing"); + }catch(Exception ex) { + OLogManager.instance().error(this, "failed to register class - " + clz.getName()); + } + } + } + + public OClusterSelectionStrategy getStrategy(final String iStrategy) { + return newInstance(iStrategy); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionStrategy.java new file mode 100644 index 00000000000..4e301bc0f7f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/OClusterSelectionStrategy.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema.clusterselection; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Strategy to select the cluster to use. Instances are stateful, so can't be reused on multiple classes. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public interface OClusterSelectionStrategy { + int getCluster(final OClass iClass, final ODocument doc); + + String getName(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ODefaultClusterSelectionStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ODefaultClusterSelectionStrategy.java new file mode 100644 index 00000000000..e3f07d071f0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ODefaultClusterSelectionStrategy.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema.clusterselection; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Returns always the first cluster configured. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ODefaultClusterSelectionStrategy implements OClusterSelectionStrategy { + public static final String NAME = "default"; + + public int getCluster(final OClass iClass, final ODocument doc) { + return iClass.getDefaultClusterId(); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ORoundRobinClusterSelectionStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ORoundRobinClusterSelectionStrategy.java new file mode 100644 index 00000000000..1bc8851177f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/clusterselection/ORoundRobinClusterSelectionStrategy.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.metadata.schema.clusterselection; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Returns the cluster selecting by round robin algorithm. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class ORoundRobinClusterSelectionStrategy implements OClusterSelectionStrategy { + public static final String NAME = "round-robin"; + private AtomicLong pointer = new AtomicLong(0); + + public int getCluster(final OClass clazz, final ODocument doc) { + final int[] clusters = clazz.getClusterIds(); + if (clusters.length == 1) + // ONLY ONE: RETURN THE FIRST ONE + return clusters[0]; + + return clusters[(int) (pointer.getAndIncrement() % clusters.length)]; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationBinaryComparable.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationBinaryComparable.java new file mode 100644 index 00000000000..d044cfeb10d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationBinaryComparable.java @@ -0,0 +1,16 @@ +package com.orientechnologies.orient.core.metadata.schema.validation; + +public class ValidationBinaryComparable implements Comparable { + + private int size; + + public ValidationBinaryComparable(int size) { + this.size = size; + } + + @Override + public int compareTo(Object o) { + return size - ((byte[]) o).length; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationCollectionComparable.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationCollectionComparable.java new file mode 100644 index 00000000000..08576e01fa5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationCollectionComparable.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.metadata.schema.validation; + +import java.util.Collection; + +public class ValidationCollectionComparable implements Comparable { + + private int size; + + public ValidationCollectionComparable(int size) { + this.size = size; + } + + @Override + public int compareTo(Object o) { + return size - ((Collection) o).size(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationMapComparable.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationMapComparable.java new file mode 100644 index 00000000000..97bf98b21e6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationMapComparable.java @@ -0,0 +1,18 @@ +package com.orientechnologies.orient.core.metadata.schema.validation; + +import java.util.Map; + +public class ValidationMapComparable implements Comparable { + + private int size; + + public ValidationMapComparable(int size) { + this.size = size; + } + + @Override + public int compareTo(Object o) { + return size - ((Map) o).size(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationStringComparable.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationStringComparable.java new file mode 100644 index 00000000000..328c150fac8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/schema/validation/ValidationStringComparable.java @@ -0,0 +1,16 @@ +package com.orientechnologies.orient.core.metadata.schema.validation; + +public class ValidationStringComparable implements Comparable { + + private int size; + + public ValidationStringComparable(int size) { + this.size = size; + } + + @Override + public int compareTo(Object o) { + return size - o.toString().length(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ODatabaseSecurityResources.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ODatabaseSecurityResources.java new file mode 100644 index 00000000000..5b4fc063746 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ODatabaseSecurityResources.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +/** + * Contains all the resources used by the Database instance to check permissions + * + * @author Luca Garulli + * @see OUser, OSecurity + */ +public class ODatabaseSecurityResources { + + public final static String ALL = "*"; + public final static String DATABASE = "database"; + public final static String SCHEMA = "database.schema"; + public final static String CLASS = "database.class"; + public final static String ALL_CLASSES = "database.class.*"; + public final static String CLUSTER = "database.cluster"; + public final static String ALL_CLUSTERS = "database.cluster.*"; + public final static String SYSTEMCLUSTERS = "database.systemclusters"; + public final static String COMMAND = "database.command"; + public final static String COMMAND_GREMLIN = "database.command.gremlin"; + public final static String FUNCTION = "database.function"; + public final static String DATABASE_CONFIG = "database.config"; + public final static String BYPASS_RESTRICTED = "database.bypassRestricted"; + public final static String RECORD_HOOK = "database.hook.record"; + public final static String SERVER_ADMIN = "server.admin"; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OIdentity.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OIdentity.java new file mode 100644 index 00000000000..3370f2d95a0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OIdentity.java @@ -0,0 +1,27 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +/** + * Created by luigidellaquila on 01/07/15. + */ +public abstract class OIdentity extends ODocumentWrapper { + public final static String CLASS_NAME = "OIdentity"; + + public OIdentity() { + } + + public OIdentity(ORID iRID) { + super(iRID); + } + + public OIdentity(String iClassName) { + super(iClassName); + } + + public OIdentity(ODocument iDocument) { + super(iDocument); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableRole.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableRole.java new file mode 100644 index 00000000000..5353b90a8ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableRole.java @@ -0,0 +1,158 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 03/11/14 + */ +public class OImmutableRole implements OSecurityRole { + private static final long serialVersionUID = 1L; + private final ALLOW_MODES mode; + private final OSecurityRole parentRole; + + private final Map rules = new HashMap(); + private final String name; + private final ORID rid; + private final ORole role; + + public OImmutableRole(ORole role) { + if (role.getParentRole() == null) + this.parentRole = null; + else + this.parentRole = new OImmutableRole(role.getParentRole()); + + this.mode = role.getMode(); + this.name = role.getName(); + this.rid = role.getIdentity().getIdentity(); + this.role = role; + + for (ORule rule : role.getRuleSet()) + rules.put(rule.getResourceGeneric(), rule); + + } + + public boolean allow(final ORule.ResourceGeneric resourceGeneric, final String resourceSpecific, final int iCRUDOperation) { + final ORule rule = rules.get(resourceGeneric); + if (rule != null) { + final Boolean allowed = rule.isAllowed(resourceSpecific, iCRUDOperation); + if (allowed != null) + return allowed; + } + + if (parentRole != null) + // DELEGATE TO THE PARENT ROLE IF ANY + return parentRole.allow(resourceGeneric, resourceSpecific, iCRUDOperation); + + return mode == ALLOW_MODES.ALLOW_ALL_BUT; + } + + public boolean hasRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific) { + ORule rule = rules.get(resourceGeneric); + + if (rule == null) + return false; + + if (resourceSpecific != null && !rule.containsSpecificResource(resourceSpecific)) + return false; + + return true; + } + + public OSecurityRole addRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + throw new UnsupportedOperationException(); + } + + public OSecurityRole grant(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + throw new UnsupportedOperationException(); + } + + public ORole revoke(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + throw new UnsupportedOperationException(); + } + + @Deprecated + @Override + public boolean allow(String iResource, int iCRUDOperation) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return allow(resourceGeneric, null, iCRUDOperation); + + return allow(resourceGeneric, specificResource, iCRUDOperation); + } + + @Deprecated + @Override + public boolean hasRule(String iResource) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return hasRule(resourceGeneric, null); + + return hasRule(resourceGeneric, specificResource); + } + + @Override + public OSecurityRole addRule(String iResource, int iOperation) { + throw new UnsupportedOperationException(); + } + + @Override + public OSecurityRole grant(String iResource, int iOperation) { + throw new UnsupportedOperationException(); + } + + @Override + public OSecurityRole revoke(String iResource, int iOperation) { + throw new UnsupportedOperationException(); + } + + public String getName() { + return name; + } + + public ALLOW_MODES getMode() { + return mode; + } + + public ORole setMode(final ALLOW_MODES iMode) { + throw new UnsupportedOperationException(); + } + + public OSecurityRole getParentRole() { + return parentRole; + } + + public ORole setParentRole(final OSecurityRole iParent) { + throw new UnsupportedOperationException(); + } + + public Set getRuleSet() { + return new HashSet(rules.values()); + } + + @Override + public String toString() { + return getName(); + } + + @Override + public OIdentifiable getIdentity() { + return rid; + } + + @Override + public ODocument getDocument() { + return role.getDocument(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableUser.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableUser.java new file mode 100644 index 00000000000..6b016b60526 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OImmutableUser.java @@ -0,0 +1,200 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.OSecurityManager; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 03/11/14 + */ +public class OImmutableUser implements OSecurityUser { + private static final long serialVersionUID = 1L; + private final long version; + + private final String name; + private final String password; + + private final Set roles = new HashSet(); + + private final STATUSES status; + private final ORID rid; + private final OUser user; + + public OImmutableUser(long version, OUser user) { + this.version = version; + this.name = user.getName(); + this.password = user.getPassword(); + this.status = user.getAccountStatus(); + this.rid = user.getIdentity().getIdentity(); + this.user = user; + + for (ORole role : user.getRoles()) { + roles.add(new OImmutableRole(role)); + } + } + + public OSecurityRole allow(final ORule.ResourceGeneric resourceGeneric, final String resourceSpecific, final int iOperation) { + if (roles.isEmpty()) + throw new OSecurityAccessException(getName(), "User '" + getName() + "' has no role defined"); + + final OSecurityRole role = checkIfAllowed(resourceGeneric, resourceSpecific, iOperation); + + if (role == null) + throw new OSecurityAccessException(getName(), "User '" + getName() + "' does not have permission to execute the operation '" + + ORole.permissionToString(iOperation) + "' against the resource: " + resourceGeneric + "." + resourceSpecific); + + return role; + } + + public OSecurityRole checkIfAllowed(final ORule.ResourceGeneric resourceGeneric, final String resourceSpecific, + final int iOperation) { + for (OImmutableRole r : roles) { + if (r == null) + OLogManager.instance().warn(this, + "User '%s' has a null role, ignoring it. Consider fixing this user's roles before continuing", getName()); + else if (r.allow(resourceGeneric, resourceSpecific, iOperation)) + return r; + } + + return null; + } + + public boolean isRuleDefined(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific) { + for (OImmutableRole r : roles) + if (r == null) + OLogManager.instance().warn(this, + "User '%s' has a null role, ignoring it. Consider fixing this user's roles before continuing", getName()); + else if (r.hasRule(resourceGeneric, resourceSpecific)) + return true; + + return false; + } + + @Override + @Deprecated + public OSecurityRole allow(String iResource, int iOperation) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return allow(resourceGeneric, null, iOperation); + + return allow(resourceGeneric, resourceSpecific, iOperation); + } + + @Override + @Deprecated + public OSecurityRole checkIfAllowed(String iResource, int iOperation) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return checkIfAllowed(resourceGeneric, null, iOperation); + + return checkIfAllowed(resourceGeneric, resourceSpecific, iOperation); + } + + @Override + @Deprecated + public boolean isRuleDefined(String iResource) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return isRuleDefined(resourceGeneric, null); + + return isRuleDefined(resourceGeneric, resourceSpecific); + } + + public boolean checkPassword(final String iPassword) { + return OSecurityManager.instance().checkPassword(iPassword, getPassword()); + } + + public String getName() { + return name; + } + + public OUser setName(final String iName) { + throw new UnsupportedOperationException(); + } + + public String getPassword() { + return password; + } + + public OUser setPassword(final String iPassword) { + throw new UnsupportedOperationException(); + } + + public STATUSES getAccountStatus() { + return status; + } + + public void setAccountStatus(STATUSES accountStatus) { + throw new UnsupportedOperationException(); + } + + public Set getRoles() { + return Collections.unmodifiableSet(roles); + } + + public OUser addRole(final String iRole) { + throw new UnsupportedOperationException(); + } + + public OUser addRole(final OSecurityRole iRole) { + throw new UnsupportedOperationException(); + } + + public boolean removeRole(final String iRoleName) { + throw new UnsupportedOperationException(); + } + + public boolean hasRole(final String iRoleName, final boolean iIncludeInherited) { + for (Iterator it = roles.iterator(); it.hasNext();) { + final OSecurityRole role = it.next(); + if (role.getName().equals(iRoleName)) + return true; + + if (iIncludeInherited) { + OSecurityRole r = role.getParentRole(); + while (r != null) { + if (r.getName().equals(iRoleName)) + return true; + r = r.getParentRole(); + } + } + } + + return false; + } + + @Override + public String toString() { + return getName(); + } + + public long getVersion() { + return version; + } + + @Override + public OIdentifiable getIdentity() { + return rid; + } + + @Override + public ODocument getDocument() { + return user.getDocument(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedAccessHook.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedAccessHook.java new file mode 100755 index 00000000000..e26d6e52602 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedAccessHook.java @@ -0,0 +1,147 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +import java.util.Set; + +/** + * Checks the access against restricted resources. Restricted resources are those documents of classes that implement ORestricted + * abstract class. + * + * @author Luca Garulli + */ +public class ORestrictedAccessHook extends ODocumentHookAbstract implements ORecordHook.Scoped { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.READ, SCOPE.UPDATE, SCOPE.DELETE }; + + public ORestrictedAccessHook(ODatabaseDocument database) { + super(database); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } + + @Override + public RESULT onRecordBeforeCreate(final ODocument iDocument) { + final OImmutableClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (cls != null && cls.isRestricted()) { + String fieldNames = cls.getCustom(OSecurityShared.ONCREATE_FIELD); + if (fieldNames == null) + fieldNames = ORestrictedOperation.ALLOW_ALL.getFieldName(); + final String[] fields = fieldNames.split(","); + String identityType = cls.getCustom(OSecurityShared.ONCREATE_IDENTITY_TYPE); + if (identityType == null) + identityType = "user"; + + OIdentifiable identity = null; + if (identityType.equals("user")) { + final OSecurityUser user = database.getUser(); + if (user != null) + identity = user.getIdentity(); + } else if (identityType.equals("role")) { + final Set roles = database.getUser().getRoles(); + if (!roles.isEmpty()) + identity = roles.iterator().next().getIdentity(); + } else + throw new OConfigurationException( + "Wrong custom field '" + OSecurityShared.ONCREATE_IDENTITY_TYPE + "' in class '" + cls.getName() + "' with value '" + + identityType + "'. Supported ones are: 'user', 'role'"); + + if (identity != null) { + for (String f : fields) + database.getMetadata().getSecurity().allowIdentity(iDocument, f, identity); + return RESULT.RECORD_CHANGED; + } + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public RESULT onRecordBeforeRead(final ODocument iDocument) { + return isAllowed(iDocument, ORestrictedOperation.ALLOW_READ, false) ? RESULT.RECORD_NOT_CHANGED : RESULT.SKIP; + } + + @Override + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + if (!isAllowed(iDocument, ORestrictedOperation.ALLOW_UPDATE, true)) + throw new OSecurityException("Cannot update record " + iDocument.getIdentity() + ": the resource has restricted access"); + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public RESULT onRecordBeforeDelete(final ODocument iDocument) { + if (!isAllowed(iDocument, ORestrictedOperation.ALLOW_DELETE, true)) + throw new OSecurityException("Cannot delete record " + iDocument.getIdentity() + ": the resource has restricted access"); + return RESULT.RECORD_NOT_CHANGED; + } + + @SuppressWarnings("unchecked") + protected boolean isAllowed(final ODocument iDocument, final ORestrictedOperation iAllowOperation, final boolean iReadOriginal) { + return isAllowed(database,iDocument,iAllowOperation,iReadOriginal); + } + + @SuppressWarnings("unchecked") + public static boolean isAllowed(ODatabaseDocument database, final ODocument iDocument, final ORestrictedOperation iAllowOperation, final boolean iReadOriginal) { + final OImmutableClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (cls != null && cls.isRestricted()) { + + if (database.getUser() == null) + return true; + + if (database.getUser().isRuleDefined(ORule.ResourceGeneric.BYPASS_RESTRICTED, null)) + if (database.getUser().checkIfAllowed(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_READ) != null) + // BYPASS RECORD LEVEL SECURITY: ONLY "ADMIN" ROLE CAN BY DEFAULT + return true; + + final ODocument doc; + if (iReadOriginal) + // RELOAD TO AVOID HACKING OF "_ALLOW" FIELDS + doc = (ODocument) database.load(iDocument.getIdentity()); + else + doc = iDocument; + + // we even not allowed to read it. + if (doc == null) + return false; + + return database.getMetadata().getSecurity() + .isAllowed((Set) doc.field(ORestrictedOperation.ALLOW_ALL.getFieldName()), + (Set) doc.field(iAllowOperation.getFieldName())); + } + + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedOperation.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedOperation.java new file mode 100644 index 00000000000..ef4689ff231 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORestrictedOperation.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +/** + * Enum containing the restricted security (Record Level Security) permissions. + * + * @author Luca Garulli + * + */ +public enum ORestrictedOperation { + /** + * Allows all RUD rights. + */ + ALLOW_ALL("_allow"), + + /** + * Allows Read rights. + */ + ALLOW_READ("_allowRead"), + + /** + * Allows Update rights. + */ + ALLOW_UPDATE("_allowUpdate"), + + /** + * Allows Delete rights. + */ + ALLOW_DELETE("_allowDelete"); + + private final String fieldName; + + ORestrictedOperation(final String iFieldName) { + fieldName = iFieldName; + } + + public String getFieldName() { + return fieldName; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORole.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORole.java new file mode 100644 index 00000000000..945272b9736 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORole.java @@ -0,0 +1,416 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.annotation.OBeforeDeserialization; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Contains the user settings about security and permissions roles.
          + * Allowed operation are the classic CRUD, namely: + *
            + *
          • CREATE
          • + *
          • READ
          • + *
          • UPDATE
          • + *
          • DELETE
          • + *
          + * Mode = ALLOW (allow all but) or DENY (deny all but) + */ +@SuppressWarnings("unchecked") +public class ORole extends OIdentity implements OSecurityRole { + public static final String ADMIN = "admin"; + public static final String CLASS_NAME = "ORole"; + public final static int PERMISSION_NONE = 0; + public final static int PERMISSION_CREATE = registerPermissionBit(0, "Create"); + public final static int PERMISSION_READ = registerPermissionBit(1, "Read"); + public final static int PERMISSION_UPDATE = registerPermissionBit(2, "Update"); + public final static int PERMISSION_DELETE = registerPermissionBit(3, "Delete"); + public final static int PERMISSION_EXECUTE = registerPermissionBit(4, "Execute"); + public final static int PERMISSION_ALL = PERMISSION_CREATE + PERMISSION_READ + PERMISSION_UPDATE + + PERMISSION_DELETE + PERMISSION_EXECUTE; + protected final static byte STREAM_DENY = 0; + protected final static byte STREAM_ALLOW = 1; + private static final long serialVersionUID = 1L; + // CRUD OPERATIONS + private static Map PERMISSION_BIT_NAMES; + protected ALLOW_MODES mode = ALLOW_MODES.DENY_ALL_BUT; + protected ORole parentRole; + + private Map rules = new HashMap(); + + /** + * Constructor used in unmarshalling. + */ + public ORole() { + } + + public ORole(final String iName, final ORole iParent, final ALLOW_MODES iAllowMode) { + super(CLASS_NAME); + document.field("name", iName); + + parentRole = iParent; + document.field("inheritedRole", iParent != null ? iParent.getDocument() : null); + setMode(iAllowMode); + + updateRolesDocumentContent(); + } + + /** + * Create the role by reading the source document. + */ + public ORole(final ODocument iSource) { + fromStream(iSource); + } + + /** + * Convert the permission code to a readable string. + * + * @param iPermission + * Permission to convert + * @return String representation of the permission + */ + public static String permissionToString(final int iPermission) { + int permission = iPermission; + final StringBuilder returnValue = new StringBuilder(128); + for (Entry p : PERMISSION_BIT_NAMES.entrySet()) { + if ((permission & p.getKey()) == p.getKey()) { + if (returnValue.length() > 0) + returnValue.append(", "); + returnValue.append(p.getValue()); + permission &= ~p.getKey(); + } + } + if (permission != 0) { + if (returnValue.length() > 0) + returnValue.append(", "); + returnValue.append("Unknown 0x"); + returnValue.append(Integer.toHexString(permission)); + } + + return returnValue.toString(); + } + + public static int registerPermissionBit(final int iBitNo, final String iName) { + if (iBitNo < 0 || iBitNo > 31) + throw new IndexOutOfBoundsException("Permission bit number must be positive and less than 32"); + + final int value = 1 << iBitNo; + if (PERMISSION_BIT_NAMES == null) + PERMISSION_BIT_NAMES = new HashMap(); + + if (PERMISSION_BIT_NAMES.containsKey(value)) + throw new IndexOutOfBoundsException("Permission bit number " + String.valueOf(iBitNo) + " already in use"); + + PERMISSION_BIT_NAMES.put(value, iName); + return value; + } + + @Override + @OBeforeDeserialization + public void fromStream(final ODocument iSource) { + if (document != null) + return; + + document = iSource; + + try { + final Number modeField = document.field("mode"); + mode = modeField == null ? ALLOW_MODES.DENY_ALL_BUT : modeField.byteValue() == STREAM_ALLOW ? ALLOW_MODES.ALLOW_ALL_BUT + : ALLOW_MODES.DENY_ALL_BUT; + } catch (Exception ex) { + OLogManager.instance().error(this, "illegal mode " + ex.getMessage()); + mode = ALLOW_MODES.DENY_ALL_BUT; + } + + final OIdentifiable role = document.field("inheritedRole"); + parentRole = role != null ? document.getDatabase().getMetadata().getSecurity().getRole(role) : null; + + boolean rolesNeedToBeUpdated = false; + Object loadedRules = document.field("rules"); + if (loadedRules instanceof Map) { + loadOldVersionOfRules((Map) loadedRules); + } else { + final Set storedRules = (Set) loadedRules; + if (storedRules != null) { + for (ODocument ruleDoc : storedRules) { + final ORule.ResourceGeneric resourceGeneric = ORule.ResourceGeneric.valueOf(ruleDoc. field("resourceGeneric")); + if(resourceGeneric==null) continue; + final Map specificResources = ruleDoc.field("specificResources"); + final Byte access = ruleDoc.field("access"); + + final ORule rule = new ORule(resourceGeneric, specificResources, access); + rules.put(resourceGeneric, rule); + } + } + + // convert the format of roles presentation to classic one + rolesNeedToBeUpdated = true; + } + + if (getName().equals("admin") && !hasRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null)) + // FIX 1.5.1 TO ASSIGN database.bypassRestricted rule to the role + addRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_ALL).save(); + + if (rolesNeedToBeUpdated) { + updateRolesDocumentContent(); + save(); + } + } + + public boolean allow(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iCRUDOperation) { + final ORule rule = rules.get(resourceGeneric); + if (rule != null) { + final Boolean allowed = rule.isAllowed(resourceSpecific, iCRUDOperation); + if (allowed != null) + return allowed; + } + + if (parentRole != null) + // DELEGATE TO THE PARENT ROLE IF ANY + return parentRole.allow(resourceGeneric, resourceSpecific, iCRUDOperation); + + return mode == ALLOW_MODES.ALLOW_ALL_BUT; + } + + public boolean hasRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific) { + ORule rule = rules.get(resourceGeneric); + + if (rule == null) + return false; + + if (resourceSpecific != null && !rule.containsSpecificResource(resourceSpecific)) + return false; + + return true; + } + + public ORole addRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + ORule rule = rules.get(resourceGeneric); + + if (rule == null) { + rule = new ORule(resourceGeneric, null, null); + rules.put(resourceGeneric, rule); + } + + rule.grantAccess(resourceSpecific, iOperation); + + rules.put(resourceGeneric, rule); + + updateRolesDocumentContent(); + + return this; + } + + @Deprecated + @Override + public boolean allow(String iResource, int iCRUDOperation) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return allow(resourceGeneric, null, iCRUDOperation); + + return allow(resourceGeneric, specificResource, iCRUDOperation); + } + + @Deprecated + @Override + public boolean hasRule(String iResource) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return hasRule(resourceGeneric, null); + + return hasRule(resourceGeneric, specificResource); + } + + @Deprecated + @Override + public OSecurityRole addRule(String iResource, int iOperation) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return addRule(resourceGeneric, null, iOperation); + + return addRule(resourceGeneric, specificResource, iOperation); + } + + @Deprecated + @Override + public OSecurityRole grant(String iResource, int iOperation) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return grant(resourceGeneric, null, iOperation); + + return grant(resourceGeneric, specificResource, iOperation); + } + + @Deprecated + @Override + public OSecurityRole revoke(String iResource, int iOperation) { + final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (specificResource == null || specificResource.equals("*")) + return revoke(resourceGeneric, null, iOperation); + + return revoke(resourceGeneric, specificResource, iOperation); + } + + /** + * Grant a permission to the resource. + * + * @return + */ + public ORole grant(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + ORule rule = rules.get(resourceGeneric); + + if (rule == null) { + rule = new ORule(resourceGeneric, null, null); + rules.put(resourceGeneric, rule); + } + + rule.grantAccess(resourceSpecific, iOperation); + + rules.put(resourceGeneric, rule); + updateRolesDocumentContent(); + return this; + } + + /** + * Revoke a permission to the resource. + */ + public ORole revoke(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + if (iOperation == PERMISSION_NONE) + return this; + + ORule rule = rules.get(resourceGeneric); + + if (rule == null) { + rule = new ORule(resourceGeneric, null, null); + rules.put(resourceGeneric, rule); + } + + rule.revokeAccess(resourceSpecific, iOperation); + rules.put(resourceGeneric, rule); + + updateRolesDocumentContent(); + + return this; + } + + public String getName() { + return document.field("name"); + } + + public ALLOW_MODES getMode() { + return mode; + } + + public ORole setMode(final ALLOW_MODES iMode) { + this.mode = iMode; + document.field("mode", mode == ALLOW_MODES.ALLOW_ALL_BUT ? STREAM_ALLOW : STREAM_DENY); + return this; + } + + public ORole getParentRole() { + return parentRole; + } + + public ORole setParentRole(final OSecurityRole iParent) { + this.parentRole = (ORole) iParent; + document.field("inheritedRole", parentRole != null ? parentRole.getDocument() : null); + return this; + } + + @Override + public ORole save() { + document.save(ORole.class.getSimpleName()); + return this; + } + + public Set getRuleSet() { + return new HashSet(rules.values()); + } + + @Deprecated + public Map getRules() { + final Map result = new HashMap(); + + for (ORule rule : rules.values()) { + String name = ORule.mapResourceGenericToLegacyResource(rule.getResourceGeneric()); + + if (rule.getAccess() != null) { + result.put(name, rule.getAccess()); + } + + for (Map.Entry specificResource : rule.getSpecificResources().entrySet()) { + result.put(name + "." + specificResource.getKey(), specificResource.getValue()); + } + } + + return result; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public OIdentifiable getIdentity() { + return document; + } + + private void loadOldVersionOfRules(final Map storedRules) { + if (storedRules != null) + for (Entry a : storedRules.entrySet()) { + ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(a.getKey()); + ORule rule = rules.get(resourceGeneric); + if (rule == null) { + rule = new ORule(resourceGeneric, null, null); + rules.put(resourceGeneric, rule); + } + + String specificResource = ORule.mapLegacyResourceToSpecificResource(a.getKey()); + if (specificResource == null || specificResource.equals("*")) { + rule.grantAccess(null, a.getValue().intValue()); + } else { + rule.grantAccess(specificResource, a.getValue().intValue()); + } + } + } + + private ODocument updateRolesDocumentContent() { + return document.field("rules", getRules()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORule.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORule.java new file mode 100644 index 00000000000..6ae92154d04 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/ORule.java @@ -0,0 +1,283 @@ +package com.orientechnologies.orient.core.metadata.security; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 08/11/14 + */ +public class ORule implements Serializable { + + public static abstract class ResourceGeneric implements Serializable { + private static final long serialVersionUID = 1L; + private static final TreeMap nameToGenericMap = new TreeMap(); + private static final TreeMap legacyToGenericMap = new TreeMap(); + private static final Map genericToLegacyMap = new HashMap(); + + public static final ResourceGeneric FUNCTION = new ResourceGeneric("FUNCTION", + ODatabaseSecurityResources.FUNCTION) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric CLASS = new ResourceGeneric("CLASS", + ODatabaseSecurityResources.CLASS) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric CLUSTER = new ResourceGeneric("CLUSTER", + ODatabaseSecurityResources.CLUSTER) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric BYPASS_RESTRICTED = new ResourceGeneric("BYPASS_RESTRICTED", + ODatabaseSecurityResources.BYPASS_RESTRICTED) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric DATABASE = new ResourceGeneric("DATABASE", + ODatabaseSecurityResources.DATABASE) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric SCHEMA = new ResourceGeneric("SCHEMA", + ODatabaseSecurityResources.SCHEMA) { + private static final long serialVersionUID = 1L; + }; + public static final ResourceGeneric COMMAND = new ResourceGeneric("COMMAND", + ODatabaseSecurityResources.COMMAND) { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric COMMAND_GREMLIN = new ResourceGeneric("COMMAND_GREMLIN", + ODatabaseSecurityResources.COMMAND_GREMLIN) { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric RECORD_HOOK = new ResourceGeneric("RECORD_HOOK", + ODatabaseSecurityResources.RECORD_HOOK) { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric SYSTEM_CLUSTERS = new ResourceGeneric("SYSTEM_CLUSTER", + ODatabaseSecurityResources.SYSTEMCLUSTERS) { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric SERVER = new ResourceGeneric("SERVER", "server") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_COPY = new ResourceGeneric("DATABASE_COPY", + "database.copy") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_CREATE = new ResourceGeneric("DATABASE_CREATE", + "database.create") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_DROP = new ResourceGeneric("DATABASE_DROP", + "database.drop") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_EXISTS = new ResourceGeneric("DATABASE_EXISTS", + "database.exists") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_FREEZE = new ResourceGeneric("DATABASE_FREEZE", + "database.freeze") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_RELEASE = new ResourceGeneric("DATABASE_RELEASE", + "database.release") { + private static final long serialVersionUID = 1L; + }; + + public static final ResourceGeneric DATABASE_PASSTHROUGH = new ResourceGeneric("DATABASE_PASSTHROUGH", + "database.passthrough") { + private static final long serialVersionUID = 1L; + }; + + private final String name; + private final String legacyName; + + protected ResourceGeneric(String name, String legacyName) { + this.name = name; + this.legacyName = legacyName != null ? legacyName : name; + register(this); + } + + public String getName() { + return name; + } + + public String getLegacyName() { + return legacyName; + } + + private static void register(ResourceGeneric resource) { + String legacyNameLowCase = resource.legacyName.toLowerCase(Locale.ENGLISH); + if (nameToGenericMap.containsKey(resource.name) || legacyToGenericMap.containsKey(resource.legacyName.toLowerCase(Locale.ENGLISH)) + || genericToLegacyMap.containsKey(resource)) { + throw new IllegalArgumentException(resource + " already registered"); + } + nameToGenericMap.put(resource.name, resource); + legacyToGenericMap.put(legacyNameLowCase, resource); + genericToLegacyMap.put(resource, resource.legacyName); + } + + public static ResourceGeneric valueOf(String name) { + return nameToGenericMap.get(name); + } + + public static ResourceGeneric[] values() { + return genericToLegacyMap.keySet().toArray(new ResourceGeneric[genericToLegacyMap.size()]); + } + + @Override + public String toString() { + return ResourceGeneric.class.getSimpleName() + " [name=" + name + ", legacyName=" + legacyName + "]"; + } + } + + private static final long serialVersionUID = 1L; + + private final ResourceGeneric resourceGeneric; + private final Map specificResources = new HashMap(); + + private Byte access = null; + + public ORule(final ResourceGeneric resourceGeneric, final Map specificResources, final Byte access) { + this.resourceGeneric = resourceGeneric; + if (specificResources != null) + this.specificResources.putAll(specificResources); + this.access = access; + } + + public static ResourceGeneric mapLegacyResourceToGenericResource(final String resource) { + final Map.Entry found = ResourceGeneric.legacyToGenericMap.floorEntry(resource.toLowerCase(Locale.ENGLISH)); + if (found == null) + return null; + + if (resource.length() < found.getKey().length()) + return null; + + if (resource.substring(0, found.getKey().length()).equalsIgnoreCase(found.getKey())) + return found.getValue(); + + return null; + } + + public static String mapResourceGenericToLegacyResource(final ResourceGeneric resourceGeneric) { + return ResourceGeneric.genericToLegacyMap.get(resourceGeneric); + } + + public static String mapLegacyResourceToSpecificResource(final String resource) { + Map.Entry found = ResourceGeneric.legacyToGenericMap.floorEntry(resource.toLowerCase(Locale.ENGLISH)); + + if (found == null) + return resource; + + if (resource.length() < found.getKey().length()) + return resource; + + if (resource.length() == found.getKey().length()) + return null; + + if (resource.substring(0, found.getKey().length()).equalsIgnoreCase(found.getKey())) + return resource.substring(found.getKey().length() + 1); + + return resource; + } + + public Byte getAccess() { + return access; + } + + public ResourceGeneric getResourceGeneric() { + return resourceGeneric; + } + + public Map getSpecificResources() { + return specificResources; + } + + public void grantAccess(String resource, final int operation) { + if (resource == null) + access = grant((byte) operation, access); + else { + resource = resource.toLowerCase(Locale.ENGLISH); + Byte ac = specificResources.get(resource); + specificResources.put(resource, grant((byte) operation, ac)); + } + } + + private byte grant(final byte operation, final Byte ac) { + if (operation == ORole.PERMISSION_NONE) + // IT'S A REVOKE + return 0; + + byte currentValue = ac == null ? ORole.PERMISSION_NONE : ac; + + currentValue |= operation; + return currentValue; + } + + public void revokeAccess(String resource, final int operation) { + if (operation == ORole.PERMISSION_NONE) + return; + + if (resource == null) + access = revoke((byte) operation, access); + else { + resource = resource.toLowerCase(Locale.ENGLISH); + final Byte ac = specificResources.get(resource); + specificResources.put(resource, revoke((byte) operation, ac)); + } + } + + private byte revoke(final byte operation, final Byte ac) { + byte currentValue; + if (ac == null) + currentValue = ORole.PERMISSION_NONE; + else { + currentValue = ac.byteValue(); + currentValue &= ~(byte) operation; + } + return currentValue; + } + + public Boolean isAllowed(final String name, final int operation) { + if (name == null) + return allowed((byte) operation, access); + + if (specificResources.isEmpty()) + return isAllowed(null, operation); + + final Byte ac = specificResources.get(name.toLowerCase(Locale.ENGLISH)); + final Boolean allowed = allowed((byte) operation, ac); + if (allowed == null) + return isAllowed(null, operation); + + return allowed; + } + + private Boolean allowed(final byte operation, final Byte ac) { + if (ac == null) + return null; + + final byte mask = (byte) operation; + + return (ac.byteValue() & mask) == mask; + } + + public boolean containsSpecificResource(final String resource) { + if (specificResources.isEmpty()) + return false; + + return specificResources.containsKey(resource.toLowerCase(Locale.ENGLISH)); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurity.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurity.java new file mode 100644 index 00000000000..3d82988afe9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurity.java @@ -0,0 +1,201 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import java.util.List; +import java.util.Set; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Manages users and roles. + * + * @author Luca Garulli + * + */ +public interface OSecurity { + static final String RESTRICTED_CLASSNAME = "ORestricted"; + @Deprecated + static final String IDENTITY_CLASSNAME = OIdentity.CLASS_NAME; + static final String ALLOW_ALL_FIELD = "_allow"; + static final String ALLOW_READ_FIELD = "_allowRead"; + static final String ALLOW_UPDATE_FIELD = "_allowUpdate"; + static final String ALLOW_DELETE_FIELD = "_allowDelete"; + static final String ONCREATE_IDENTITY_TYPE = "onCreate.identityType"; + static final String ONCREATE_FIELD = "onCreate.fields"; + + OUser create(); + + void load(); + + boolean isAllowed(final Set iAllowAll, final Set iAllowOperation); + + /** + * Record level security: allows a user to access to a record. + * + * @param iDocument + * ODocument instance to give access + * @param iOperationType + * Operation type to use based on the permission to allow: + *
            + *
          • ALLOW_ALL, to provide full access (RUD)
          • + *
          • ALLOW_READ, to provide read access
          • + *
          • ALLOW_UPDATE, to provide update access
          • + *
          • ALLOW_DELETE, to provide delete access
          • + *
          + * @param iUserName + * User name to provide the access + * @return The OIdentity instance allowed + */ + OIdentifiable allowUser(final ODocument iDocument, final ORestrictedOperation iOperationType, final String iUserName); + + /** + * Record level security: allows a role to access to a record. + * + * @param iDocument + * ODocument instance to give access + * @param iOperationType + * Operation type to use based on the permission to allow: + *
            + *
          • ALLOW_ALL, to provide full access (RUD)
          • + *
          • ALLOW_READ, to provide read access
          • + *
          • ALLOW_UPDATE, to provide update access
          • + *
          • ALLOW_DELETE, to provide delete access
          • + *
          + * @param iRoleName + * Role name to provide the access + * @return The OIdentity instance allowed + */ + OIdentifiable allowRole(final ODocument iDocument, final ORestrictedOperation iOperationType, final String iRoleName); + + /** + * Record level security: deny a user to access to a record. + * + * @param iDocument + * ODocument instance to give access + * @param iOperationType + * Operation type to use based on the permission to deny: + *
            + *
          • ALLOW_ALL, to provide full access (RUD)
          • + *
          • ALLOW_READ, to provide read access
          • + *
          • ALLOW_UPDATE, to provide update access
          • + *
          • ALLOW_DELETE, to provide delete access
          • + *
          + * @param iUserName + * User name to deny the access + * @return The OIdentity instance denied + */ + OIdentifiable denyUser(final ODocument iDocument, final ORestrictedOperation iOperationType, final String iUserName); + + /** + * Record level security: deny a role to access to a record. + * + * @param iDocument + * ODocument instance to give access + * @param iOperationType + * Operation type to use based on the permission to deny: + *
            + *
          • ALLOW_ALL, to provide full access (RUD)
          • + *
          • ALLOW_READ, to provide read access
          • + *
          • ALLOW_UPDATE, to provide update access
          • + *
          • ALLOW_DELETE, to provide delete access
          • + *
          + * @param iRoleName + * Role name to deny the access + * @return The OIdentity instance denied + */ + OIdentifiable denyRole(final ODocument iDocument, final ORestrictedOperation iOperationType, final String iRoleName); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable allowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable allowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable allowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable disallowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable disallowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName); + + /** + * Uses the version with ENUM instead. + */ + @Deprecated + OIdentifiable disallowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId); + + OUser authenticate(String iUsername, String iUserPassword); + + OUser authenticate(final OToken authToken); + + OUser getUser(String iUserName); + + OUser getUser(final ORID iUserId); + + OUser createUser(String iUserName, String iUserPassword, String... iRoles); + + OUser createUser(String iUserName, String iUserPassword, ORole... iRoles); + + boolean dropUser(String iUserName); + + ORole getRole(String iRoleName); + + ORole getRole(OIdentifiable role); + + ORole createRole(String iRoleName, ORole.ALLOW_MODES iAllowMode); + + ORole createRole(String iRoleName, ORole iParent, ORole.ALLOW_MODES iAllowMode); + + boolean dropRole(String iRoleName); + + List getAllUsers(); + + List getAllRoles(); + + void close(boolean onDelete); + + void createClassTrigger(); + + OSecurity getUnderlying(); + + long getVersion(); + + void incrementVersion(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityExternal.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityExternal.java new file mode 100644 index 00000000000..5db961df940 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityExternal.java @@ -0,0 +1,98 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.storage.OStorageProxy; + +import com.orientechnologies.orient.core.Orient; + +/** + * OSecurity implementation that extends OSecurityShared but uses an external security plugin. + * + * @author S. Colin Leister + * + */ +public class OSecurityExternal extends OSecurityShared +{ + @Override + public OUser authenticate(final String iUsername, final String iUserPassword) + { + OUser user = null; + final String dbName = getDatabase().getName(); + + if(!(getDatabase().getStorage() instanceof OStorageProxy)) + { + if(Orient.instance().getSecurity() == null) throw new OSecurityAccessException(dbName, "External Security System is null!"); + + // Uses the external authenticator. + // username is returned if authentication is successful, otherwise null. + String username = Orient.instance().getSecurity().authenticate(iUsername, iUserPassword); + + if(username != null) + { + user = getUser(username); + + if(user == null) throw new OSecurityAccessException(dbName, "User or password not valid for username: " + username + ", database: '" + dbName + "'"); + + if(user.getAccountStatus() != OSecurityUser.STATUSES.ACTIVE) throw new OSecurityAccessException(dbName, "User '" + username + "' is not active"); + } + else + { + // Will use the local database to authenticate. + if(Orient.instance().getSecurity().isDefaultAllowed()) + { + user = super.authenticate(iUsername, iUserPassword); + } + else + { + // WAIT A BIT TO AVOID BRUTE FORCE + try + { + Thread.sleep(200); + } + catch(InterruptedException e) + { + Thread.currentThread().interrupt(); + } + + throw new OSecurityAccessException(dbName, "User or password not valid for username: " + iUsername + ", database: '" + dbName + "'"); + } + } + } + + return user; + } + + @Override + public OUser getUser(final String username) { + OUser user = null; + + if (Orient.instance().getSecurity() != null) { + // See if there's a system user first. + user = Orient.instance().getSecurity().getSystemUser(username, getDatabase().getName()); + } + + // If not found, try the local database. + if(user == null) user = super.getUser(username); + + return user; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityNull.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityNull.java new file mode 100644 index 00000000000..39a38e4314a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityNull.java @@ -0,0 +1,182 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.List; +import java.util.Set; + +/** + * Dummy security implementation. + * + * @author Luca Garulli + * + */ +public class OSecurityNull implements OSecurity { + public OSecurityNull(final OSecurity iDelegate, final ODatabaseDocumentInternal iDatabase) { + } + + @Override + public boolean isAllowed(final Set iAllowAll, final Set iAllowOperation) { + return true; + } + + @Override + public OIdentifiable allowUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return null; + } + + @Override + public OIdentifiable allowRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return null; + } + + @Override + public OIdentifiable denyUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return null; + } + + @Override + public OIdentifiable denyRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return null; + } + + public OUser create() { + return null; + } + + public void load() { + } + + public OUser getUser(String iUserName) { + return null; + } + + public OUser getUser(ORID iUserId) { + return null; + } + + public OUser createUser(String iUserName, String iUserPassword, String... iRoles) { + return null; + } + + public OUser createUser(String iUserName, String iUserPassword, ORole... iRoles) { + return null; + } + + public ORole getRole(String iRoleName) { + return null; + } + + public ORole getRole(OIdentifiable iRole) { + return null; + } + + public ORole createRole(String iRoleName, OSecurityRole.ALLOW_MODES iAllowMode) { + return null; + } + + public ORole createRole(String iRoleName, ORole iParent, OSecurityRole.ALLOW_MODES iAllowMode) { + return null; + } + + public List getAllUsers() { + return null; + } + + public List getAllRoles() { + return null; + } + + public OUser authenticate(String iUsername, String iUserPassword) { + return null; + } + + public OUser authenticate(OToken authToken) { + return null; + } + + public void close(boolean onDelete) { + } + + public OUser repair() { + return null; + } + + public boolean dropUser(String iUserName) { + return false; + } + + public boolean dropRole(String iRoleName) { + return false; + } + + @Override + public OIdentifiable allowUser(ODocument iDocument, String iAllowFieldName, String iUserName) { + return null; + } + + @Override + public OIdentifiable allowRole(ODocument iDocument, String iAllowFieldName, String iRoleName) { + return null; + } + + @Override + public OIdentifiable allowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return null; + } + + @Override + public OIdentifiable disallowUser(ODocument iDocument, String iAllowFieldName, String iUserName) { + return null; + } + + @Override + public OIdentifiable disallowRole(ODocument iDocument, String iAllowFieldName, String iRoleName) { + return null; + } + + @Override + public OIdentifiable disallowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return null; + } + + @Override + public void createClassTrigger() { + } + + @Override + public OSecurity getUnderlying() { + return null; + } + + @Override + public long getVersion() { + return 0; + } + + @Override + public void incrementVersion() { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityProxy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityProxy.java new file mode 100644 index 00000000000..cae4ac9e006 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityProxy.java @@ -0,0 +1,184 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.OProxedResource; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.List; +import java.util.Set; + +/** + * Proxy class for user management + * + * @author Luca Garulli + * + */ +public class OSecurityProxy extends OProxedResource implements OSecurity { + public OSecurityProxy(final OSecurity iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + @Override + public boolean isAllowed(final Set iAllowAll, final Set iAllowOperation) { + return delegate.isAllowed(iAllowAll, iAllowOperation); + } + + @Override + public OIdentifiable allowUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return delegate.allowUser(iDocument, iOperationType, iUserName); + } + + @Override + public OIdentifiable allowRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return delegate.allowRole(iDocument, iOperationType, iRoleName); + } + + @Override + public OIdentifiable denyUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return delegate.denyUser(iDocument, iOperationType, iUserName); + } + + @Override + public OIdentifiable denyRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return delegate.denyRole(iDocument, iOperationType, iRoleName); + } + + public OIdentifiable allowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + return delegate.allowUser(iDocument, iAllowFieldName, iUserName); + } + + public OIdentifiable allowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + return delegate.allowRole(iDocument, iAllowFieldName, iRoleName); + } + + @Override + public OIdentifiable allowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return delegate.allowIdentity(iDocument, iAllowFieldName, iId); + } + + public OIdentifiable disallowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + return delegate.disallowUser(iDocument, iAllowFieldName, iUserName); + } + + public OIdentifiable disallowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + return delegate.disallowRole(iDocument, iAllowFieldName, iRoleName); + } + + @Override + public OIdentifiable disallowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return delegate.disallowIdentity(iDocument, iAllowFieldName, iId); + } + + public OUser create() { + return delegate.create(); + } + + public void load() { + delegate.load(); + } + + public void close(boolean onDelete) { + if (delegate != null) + delegate.close(false); + } + + public OUser authenticate(final String iUsername, final String iUserPassword) { + return delegate.authenticate(iUsername, iUserPassword); + } + + public OUser authenticate(final OToken authToken) { + return delegate.authenticate(authToken); + } + + public OUser getUser(final String iUserName) { + return delegate.getUser(iUserName); + } + + public OUser getUser(final ORID iUserId) { + return delegate.getUser(iUserId); + } + + public OUser createUser(final String iUserName, final String iUserPassword, final String... iRoles) { + return delegate.createUser(iUserName, iUserPassword, iRoles); + } + + public OUser createUser(final String iUserName, final String iUserPassword, final ORole... iRoles) { + return delegate.createUser(iUserName, iUserPassword, iRoles); + } + + public ORole getRole(final String iRoleName) { + return delegate.getRole(iRoleName); + } + + public ORole getRole(final OIdentifiable iRole) { + return delegate.getRole(iRole); + } + + public ORole createRole(final String iRoleName, final OSecurityRole.ALLOW_MODES iAllowMode) { + return delegate.createRole(iRoleName, iAllowMode); + } + + public ORole createRole(final String iRoleName, final ORole iParent, final OSecurityRole.ALLOW_MODES iAllowMode) { + return delegate.createRole(iRoleName, iParent, iAllowMode); + } + + public List getAllUsers() { + return delegate.getAllUsers(); + } + + public List getAllRoles() { + return delegate.getAllRoles(); + } + + public String toString() { + return delegate.toString(); + } + + public boolean dropUser(final String iUserName) { + return delegate.dropUser(iUserName); + } + + public boolean dropRole(final String iRoleName) { + return delegate.dropRole(iRoleName); + } + + public void createClassTrigger() { + delegate.createClassTrigger(); + } + + @Override + public OSecurity getUnderlying() { + return delegate; + } + + @Override + public long getVersion() { + return delegate.getVersion(); + } + + @Override + public void incrementVersion() { + delegate.incrementVersion(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityRole.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityRole.java new file mode 100644 index 00000000000..fd533c1e32f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityRole.java @@ -0,0 +1,58 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.io.Serializable; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 03/11/14 + */ +public interface OSecurityRole extends Serializable { + public enum ALLOW_MODES { + DENY_ALL_BUT, ALLOW_ALL_BUT + } + + public boolean allow(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iCRUDOperation); + + public boolean hasRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific); + + public OSecurityRole addRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation); + + public OSecurityRole grant(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation); + + public OSecurityRole revoke(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation); + + @Deprecated + public boolean allow(final String iResource, final int iCRUDOperation); + + @Deprecated + public boolean hasRule(final String iResource); + + @Deprecated + public OSecurityRole addRule(final String iResource, final int iOperation); + + @Deprecated + public OSecurityRole grant(final String iResource, final int iOperation); + + @Deprecated + public OSecurityRole revoke(final String iResource, final int iOperation); + + public String getName(); + + public OSecurityRole.ALLOW_MODES getMode(); + + public OSecurityRole setMode(final OSecurityRole.ALLOW_MODES iMode); + + public OSecurityRole getParentRole(); + + public OSecurityRole setParentRole(final OSecurityRole iParent); + + public Set getRuleSet(); + + public OIdentifiable getIdentity(); + + public ODocument getDocument(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityShared.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityShared.java new file mode 100755 index 00000000000..ea07c386ce7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityShared.java @@ -0,0 +1,638 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.concur.resource.OCloseable; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OClassTrigger; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.ONullOutputListener; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClass.INDEX_TYPE; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser.STATUSES; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import com.orientechnologies.orient.core.storage.OStorageProxy; +import com.orientechnologies.orient.core.Orient; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Shared security class. It's shared by all the database instances that point to the same storage. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSecurityShared implements OSecurity, OCloseable { + private final AtomicLong version = new AtomicLong(); + + public static final String RESTRICTED_CLASSNAME = "ORestricted"; + public static final String IDENTITY_CLASSNAME = "OIdentity"; + + /** + * Uses the ORestrictedOperation ENUM instead. + */ + @Deprecated + public static final String ALLOW_ALL_FIELD = ORestrictedOperation.ALLOW_ALL.getFieldName(); + + /** + * Uses the ORestrictedOperation ENUM instead. + */ + @Deprecated + public static final String ALLOW_READ_FIELD = ORestrictedOperation.ALLOW_READ.getFieldName(); + + /** + * Uses the ORestrictedOperation ENUM instead. + */ + @Deprecated + public static final String ALLOW_UPDATE_FIELD = ORestrictedOperation.ALLOW_UPDATE.getFieldName(); + + /** + * Uses the ORestrictedOperation ENUM instead. + */ + @Deprecated + public static final String ALLOW_DELETE_FIELD = ORestrictedOperation.ALLOW_DELETE.getFieldName(); + + public static final String ONCREATE_IDENTITY_TYPE = "onCreate.identityType"; + public static final String ONCREATE_FIELD = "onCreate.fields"; + + public static final Set ALLOW_FIELDS = Collections.unmodifiableSet(new HashSet() { + { + add(ORestrictedOperation.ALLOW_ALL.getFieldName()); + add(ORestrictedOperation.ALLOW_READ.getFieldName()); + add(ORestrictedOperation.ALLOW_UPDATE.getFieldName()); + add(ORestrictedOperation.ALLOW_DELETE.getFieldName()); + } + }); + + public OSecurityShared() { + } + + @Override + public OIdentifiable allowRole(final ODocument iDocument, final ORestrictedOperation iOperation, final String iRoleName) { + final ORID role = getRoleRID(iRoleName); + if (role == null) + throw new IllegalArgumentException("Role '" + iRoleName + "' not found"); + + return allowIdentity(iDocument, iOperation.getFieldName(), role); + } + + @Override + public OIdentifiable allowUser(final ODocument iDocument, final ORestrictedOperation iOperation, final String iUserName) { + final ORID user = getUserRID(iUserName); + if (user == null) + throw new IllegalArgumentException("User '" + iUserName + "' not found"); + + return allowIdentity(iDocument, iOperation.getFieldName(), user); + } + + @Override + public OIdentifiable allowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + final ORID user = getUserRID(iUserName); + if (user == null) + throw new IllegalArgumentException("User '" + iUserName + "' not found"); + + return allowIdentity(iDocument, iAllowFieldName, user); + } + + @Override + public OIdentifiable allowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + final ORID role = getRoleRID(iRoleName); + if (role == null) + throw new IllegalArgumentException("Role '" + iRoleName + "' not found"); + + return allowIdentity(iDocument, iAllowFieldName, role); + } + + public OIdentifiable allowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId) { + Set field = iDocument.field(iAllowFieldName); + if (field == null) { + field = new ORecordLazySet(iDocument); + iDocument.field(iAllowFieldName, field); + } + field.add(iId); + + return iId; + } + + @Override + public OIdentifiable denyUser(final ODocument iDocument, final ORestrictedOperation iOperation, final String iUserName) { + final ORID user = getUserRID(iUserName); + if (user == null) + throw new IllegalArgumentException("User '" + iUserName + "' not found"); + + return disallowIdentity(iDocument, iOperation.getFieldName(), user); + } + + @Override + public OIdentifiable denyRole(final ODocument iDocument, final ORestrictedOperation iOperation, final String iRoleName) { + final ORID role = getRoleRID(iRoleName); + if (role == null) + throw new IllegalArgumentException("Role '" + iRoleName + "' not found"); + + return disallowIdentity(iDocument, iOperation.getFieldName(), role); + } + + @Override + public OIdentifiable disallowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + final ORID user = getUserRID(iUserName); + if (user == null) + throw new IllegalArgumentException("User '" + iUserName + "' not found"); + + return disallowIdentity(iDocument, iAllowFieldName, user); + } + + @Override + public OIdentifiable disallowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + final ORID role = getRoleRID(iRoleName); + if (role == null) + throw new IllegalArgumentException("Role '" + iRoleName + "' not found"); + + return disallowIdentity(iDocument, iAllowFieldName, role); + } + + public OIdentifiable disallowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId) { + Set field = iDocument.field(iAllowFieldName); + if (field != null) + field.remove(iId); + return iId; + } + + @Override + public boolean isAllowed(final Set iAllowAll, final Set iAllowOperation) { + if ((iAllowAll == null || iAllowAll.isEmpty()) && (iAllowOperation == null || iAllowOperation.isEmpty())) + // NO AUTHORIZATION: CAN'T ACCESS + return false; + + final OSecurityUser currentUser = ODatabaseRecordThreadLocal.INSTANCE.get().getUser(); + if (currentUser != null) { + // CHECK IF CURRENT USER IS ENLISTED + if (iAllowAll == null || (iAllowAll != null && !iAllowAll.contains(currentUser.getIdentity()))) { + // CHECK AGAINST SPECIFIC _ALLOW OPERATION + if (iAllowOperation != null && iAllowOperation.contains(currentUser.getIdentity())) + return true; + + // CHECK IF AT LEAST ONE OF THE USER'S ROLES IS ENLISTED + for (OSecurityRole r : currentUser.getRoles()) { + // CHECK AGAINST GENERIC _ALLOW + if (iAllowAll != null && iAllowAll.contains(r.getIdentity())) + return true; + // CHECK AGAINST SPECIFIC _ALLOW OPERATION + if (iAllowOperation != null && iAllowOperation.contains(r.getIdentity())) + return true; + // CHECK inherited permissions from parent roles, fixes #1980: Record Level Security: permissions don't follow role's + // inheritance + OSecurityRole parentRole = r.getParentRole(); + while (parentRole != null) { + if (iAllowAll != null && iAllowAll.contains(parentRole.getIdentity())) + return true; + if (iAllowOperation != null && iAllowOperation.contains(parentRole.getIdentity())) + return true; + parentRole = parentRole.getParentRole(); + } + } + return false; + } + } + return true; + } + + public OUser authenticate(final String iUserName, final String iUserPassword) { + final String dbName = getDatabase().getName(); + final OUser user = getUser(iUserName); + if (user == null) + throw new OSecurityAccessException(dbName, "User or password not valid for database: '" + dbName + "'"); + + if (user.getAccountStatus() != OSecurityUser.STATUSES.ACTIVE) + throw new OSecurityAccessException(dbName, "User '" + iUserName + "' is not active"); + + if (!(getDatabase().getStorage() instanceof OStorageProxy)) { + // CHECK USER & PASSWORD + if (!user.checkPassword(iUserPassword)) { + // WAIT A BIT TO AVOID BRUTE FORCE + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + throw new OSecurityAccessException(dbName, "User or password not valid for database: '" + dbName + "'"); + } + } + + return user; + } + + // Token MUST be validated before being passed to this method. + public OUser authenticate(final OToken authToken) { + final String dbName = getDatabase().getName(); + if (authToken.getIsValid() != true) { + throw new OSecurityAccessException(dbName, "Token not valid"); + } + + OUser user = authToken.getUser(getDatabase()); + if (user == null && authToken.getUserName() != null) { + // Token handler may not support returning an OUser so let's get username (subject) and query: + user = getUser(authToken.getUserName()); + } + + if (user == null) { + throw new OSecurityAccessException(dbName, "Authentication failed, could not load user from token"); + } + if (user.getAccountStatus() != STATUSES.ACTIVE) + throw new OSecurityAccessException(dbName, "User '" + user.getName() + "' is not active"); + + return user; + } + + public OUser getUser(final ORID iRecordId) { + if (iRecordId == null) + return null; + + ODocument result; + result = getDatabase().load(iRecordId, "roles:1"); + if (!result.getClassName().equals(OUser.CLASS_NAME)) { + result = null; + } + return new OUser(result); + } + + public OUser createUser(final String iUserName, final String iUserPassword, final String... iRoles) { + final OUser user = new OUser(iUserName, iUserPassword); + + if (iRoles != null) + for (String r : iRoles) { + user.addRole(r); + } + + return user.save(); + } + + public OUser createUser(final String userName, final String userPassword, final ORole... roles) { + final OUser user = new OUser(userName, userPassword); + + if (roles != null) + for (ORole r : roles) { + user.addRole(r); + } + + return user.save(); + } + + public boolean dropUser(final String iUserName) { + final Number removed = getDatabase().command(new OCommandSQL("delete from OUser where name = ?")) + .execute(iUserName); + + return removed != null && removed.intValue() > 0; + } + + public ORole getRole(final OIdentifiable iRole) { + final ODocument doc = iRole.getRecord(); + if (doc != null && "ORole".equals(doc.getClassName())) + return new ORole(doc); + + return null; + } + + public ORole getRole(final String iRoleName) { + if (iRoleName == null) + return null; + + final List result = getDatabase().command( + new OSQLSynchQuery("select from ORole where name = ? limit 1")).execute(iRoleName); + + if (result != null && !result.isEmpty()) + return new ORole(result.get(0)); + + return null; + } + + public ORID getRoleRID(final String iRoleName) { + if (iRoleName == null) + return null; + + final List result = getDatabase().command( + new OSQLSynchQuery("select rid from index:ORole.name where key = ? limit 1")).execute(iRoleName); + + if (result != null && !result.isEmpty()) + return result.get(0).rawField("rid"); + + return null; + } + + public ORole createRole(final String iRoleName, final ORole.ALLOW_MODES iAllowMode) { + return createRole(iRoleName, null, iAllowMode); + } + + public ORole createRole(final String iRoleName, final ORole iParent, final ORole.ALLOW_MODES iAllowMode) { + final ORole role = new ORole(iRoleName, iParent, iAllowMode); + return role.save(); + } + + public boolean dropRole(final String iRoleName) { + final Number removed = getDatabase().command( + new OCommandSQL("delete from ORole where name = '" + iRoleName + "'")).execute(); + + return removed != null && removed.intValue() > 0; + } + + public List getAllUsers() { + return getDatabase().command(new OSQLSynchQuery("select from OUser")).execute(); + } + + public List getAllRoles() { + return getDatabase().command(new OSQLSynchQuery("select from ORole")).execute(); + } + + public OUser create() { + if (!getDatabase().getMetadata().getSchema().getClasses().isEmpty()) + return null; + + final OUser adminUser = createMetadata(); + + final ORole readerRole = createRole("reader", ORole.ALLOW_MODES.DENY_ALL_BUT); + readerRole.addRule(ORule.ResourceGeneric.DATABASE, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.SCHEMA, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, OMetadataDefault.CLUSTER_INTERNAL_NAME, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "orole", ORole.PERMISSION_NONE); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "ouser", ORole.PERMISSION_NONE); + readerRole.addRule(ORule.ResourceGeneric.CLASS, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.CLASS, "OUser", ORole.PERMISSION_NONE); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.COMMAND, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.RECORD_HOOK, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.FUNCTION, null, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_NONE); + readerRole.save(); + + // This will return the global value if a local storage context configuration value does not exist. + boolean createDefUsers = getDatabase().getStorage().getConfiguration().getContextConfiguration().getValueAsBoolean(OGlobalConfiguration.CREATE_DEFAULT_USERS); + + if(createDefUsers) + createUser("reader", "reader", new String[] { readerRole.getName() }); + + final ORole writerRole = createRole("writer", ORole.ALLOW_MODES.DENY_ALL_BUT); + writerRole.addRule(ORule.ResourceGeneric.DATABASE, null, ORole.PERMISSION_READ); + writerRole.addRule(ORule.ResourceGeneric.SCHEMA, null, ORole.PERMISSION_READ); + writerRole.addRule(ORule.ResourceGeneric.CLUSTER, OMetadataDefault.CLUSTER_INTERNAL_NAME, ORole.PERMISSION_READ); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "orole", ORole.PERMISSION_NONE); + readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "ouser", ORole.PERMISSION_NONE); + writerRole.addRule(ORule.ResourceGeneric.CLASS, null, ORole.PERMISSION_ALL); + writerRole.addRule(ORule.ResourceGeneric.CLASS, "OUser", ORole.PERMISSION_NONE); + writerRole.addRule(ORule.ResourceGeneric.CLUSTER, null, ORole.PERMISSION_ALL); + writerRole.addRule(ORule.ResourceGeneric.COMMAND, null, ORole.PERMISSION_ALL); + writerRole.addRule(ORule.ResourceGeneric.RECORD_HOOK, null, ORole.PERMISSION_ALL); + writerRole.addRule(ORule.ResourceGeneric.FUNCTION, null, ORole.PERMISSION_READ); + writerRole.addRule(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_NONE); + writerRole.save(); + + if(createDefUsers) + createUser("writer", "writer", new String[] { writerRole.getName() }); + + return adminUser; + } + + /** + * Repairs the security structure if broken by creating the ADMIN role and user with default password. + * + * @return + */ + + public OUser createMetadata() { + final ODatabaseDocument database = getDatabase(); + + OClass identityClass = database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME); // SINCE 1.2.0 + if (identityClass == null) + identityClass = database.getMetadata().getSchema().createAbstractClass(OIdentity.CLASS_NAME); + + OClass roleClass = createOrUpdateORoleClass(database, identityClass); + + createOrUpdateOUserClass(database, identityClass, roleClass); + + // CREATE ROLES AND USERS + ORole adminRole = getRole(ORole.ADMIN); + if (adminRole == null) { + adminRole = createRole(ORole.ADMIN, ORole.ALLOW_MODES.ALLOW_ALL_BUT); + adminRole.addRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_ALL).save(); + } + + OUser adminUser = getUser(OUser.ADMIN); + + if (adminUser == null) { + // This will return the global value if a local storage context configuration value does not exist. + boolean createDefUsers = getDatabase().getStorage().getConfiguration().getContextConfiguration().getValueAsBoolean(OGlobalConfiguration.CREATE_DEFAULT_USERS); + + if(createDefUsers) { + adminUser = createUser(OUser.ADMIN, OUser.ADMIN, adminRole); + } + } + + // SINCE 1.2.0 + createOrUpdateORestrictedClass(database); + + return adminUser; + } + + private void createOrUpdateORestrictedClass(final ODatabaseDocument database) { + OClass restrictedClass = database.getMetadata().getSchema().getClass(RESTRICTED_CLASSNAME); + boolean unsafe = false; + if (restrictedClass == null) { + restrictedClass = database.getMetadata().getSchema().createAbstractClass(RESTRICTED_CLASSNAME); + unsafe = true; + } + if (!restrictedClass.existsProperty(ALLOW_ALL_FIELD)) + restrictedClass + .createProperty(ALLOW_ALL_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME), + unsafe); + if (!restrictedClass.existsProperty(ALLOW_READ_FIELD)) + restrictedClass + .createProperty(ALLOW_READ_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME), + unsafe); + if (!restrictedClass.existsProperty(ALLOW_UPDATE_FIELD)) + restrictedClass + .createProperty(ALLOW_UPDATE_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME), + unsafe); + if (!restrictedClass.existsProperty(ALLOW_DELETE_FIELD)) + restrictedClass + .createProperty(ALLOW_DELETE_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME), + unsafe); + } + + private void createOrUpdateOUserClass(final ODatabaseDocument database, OClass identityClass, OClass roleClass) { + boolean unsafe = false; + OClass userClass = database.getMetadata().getSchema().getClass("OUser"); + if (userClass == null) { + userClass = database.getMetadata().getSchema().createClass("OUser", identityClass); + unsafe = true; + } else if (!userClass.getSuperClasses().contains(identityClass)) + // MIGRATE AUTOMATICALLY TO 1.2.0 + userClass.setSuperClasses(Arrays.asList(identityClass)); + + if (!userClass.existsProperty("name")) { + ((OClassImpl) userClass).createProperty("name", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true) + .setCollate("ci").setMin("1").setRegexp("\\S+(.*\\S+)*"); + userClass.createIndex("OUser.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name"); + } else { + final OProperty name = userClass.getProperty("name"); + if (name.getAllIndexes().isEmpty()) + userClass.createIndex("OUser.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name"); + } + if (!userClass.existsProperty(OUser.PASSWORD_FIELD)) + userClass.createProperty(OUser.PASSWORD_FIELD, OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true); + if (!userClass.existsProperty("roles")) + userClass.createProperty("roles", OType.LINKSET, roleClass, unsafe); + if (!userClass.existsProperty("status")) + userClass.createProperty("status", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true); + } + + private OClass createOrUpdateORoleClass(final ODatabaseDocument database, OClass identityClass) { + OClass roleClass = database.getMetadata().getSchema().getClass("ORole"); + boolean unsafe = false; + if (roleClass == null) { + roleClass = database.getMetadata().getSchema().createClass("ORole", identityClass); + unsafe = true; + } else if (!roleClass.getSuperClasses().contains(identityClass)) + // MIGRATE AUTOMATICALLY TO 1.2.0 + roleClass.setSuperClasses(Arrays.asList(identityClass)); + + if (!roleClass.existsProperty("name")) { + roleClass.createProperty("name", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true).setCollate("ci"); + roleClass.createIndex("ORole.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name"); + } else { + final OProperty name = roleClass.getProperty("name"); + if (name.getAllIndexes().isEmpty()) + roleClass.createIndex("ORole.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name"); + } + + if (!roleClass.existsProperty("mode")) + roleClass.createProperty("mode", OType.BYTE, (OType) null, unsafe); + + if (!roleClass.existsProperty("rules")) + roleClass.createProperty("rules", OType.EMBEDDEDMAP, OType.BYTE, unsafe); + if (!roleClass.existsProperty("inheritedRole")) + roleClass.createProperty("inheritedRole", OType.LINK, roleClass, unsafe); + return roleClass; + } + + @Override + public void close() { + } + + @Override + public void close(boolean onDelete) { + } + + public void load() { + final OClass userClass = getDatabase().getMetadata().getSchema().getClass("OUser"); + if (userClass != null) { + // @COMPATIBILITY <1.3.0 + if (!userClass.existsProperty("status")) { + userClass.createProperty("status", OType.STRING).setMandatory(true).setNotNull(true); + } + OProperty p = userClass.getProperty("name"); + if (p == null) + p = userClass.createProperty("name", OType.STRING).setMandatory(true).setNotNull(true).setMin("1") + .setRegexp("\\S+(.*\\S+)*"); + + if (userClass.getInvolvedIndexes("name") == null) + p.createIndex(INDEX_TYPE.UNIQUE); + + // ROLE + final OClass roleClass = getDatabase().getMetadata().getSchema().getClass("ORole"); + + final OProperty rules = roleClass.getProperty("rules"); + if (rules != null && !OType.EMBEDDEDMAP.equals(rules.getType())) { + roleClass.dropProperty("rules"); + } + + if (!roleClass.existsProperty("inheritedRole")) { + roleClass.createProperty("inheritedRole", OType.LINK, roleClass); + } + + p = roleClass.getProperty("name"); + if (p == null) + p = roleClass.createProperty("name", OType.STRING).setMandatory(true).setNotNull(true); + + if (roleClass.getInvolvedIndexes("name") == null) + p.createIndex(INDEX_TYPE.UNIQUE); + } + } + + public void createClassTrigger() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + OClass classTrigger = db.getMetadata().getSchema().getClass(OClassTrigger.CLASSNAME); + if (classTrigger == null) + classTrigger = db.getMetadata().getSchema().createAbstractClass(OClassTrigger.CLASSNAME); + } + + @Override + public OSecurity getUnderlying() { + return this; + } + + public OUser getUser(final String iUserName) { + List result = getDatabase().command( + new OSQLSynchQuery("select from OUser where name = ? limit 1").setFetchPlan("roles:1")).execute(iUserName); + + if (result != null && !result.isEmpty()) + return new OUser(result.get(0)); + + return null; + } + + public ORID getUserRID(final String iUserName) { + List result = getDatabase().command( + new OSQLSynchQuery("select rid from index:OUser.name where key = ? limit 1")).execute(iUserName); + + if (result != null && !result.isEmpty()) + return result.get(0).rawField("rid"); + + return null; + } + + @Override + public long getVersion() { + return version.get(); + } + + @Override + public void incrementVersion() { + version.incrementAndGet(); + } + + protected ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityTrackerHook.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityTrackerHook.java new file mode 100644 index 00000000000..27f7f5741b3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityTrackerHook.java @@ -0,0 +1,84 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +import java.lang.ref.WeakReference; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 04/11/14 + */ +public class OSecurityTrackerHook extends ODocumentHookAbstract implements ORecordHook.Scoped { + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + private final WeakReference security; + + public OSecurityTrackerHook(OSecurity security, ODatabaseDocument database) { + super(database); + this.security = new WeakReference(security); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + @Override + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE; + } + + @Override + public void onRecordAfterCreate(ODocument doc) { + incrementSchemaVersion(doc); + } + + @Override + public void onRecordAfterUpdate(ODocument doc) { + incrementSchemaVersion(doc); + } + + @Override + public void onRecordAfterDelete(ODocument doc) { + incrementSchemaVersion(doc); + } + + @Override + public void onRecordCreateReplicated(ODocument doc) { + incrementSchemaVersion(doc); + } + + @Override + public void onRecordUpdateReplicated(ODocument doc) { + incrementSchemaVersion(doc); + } + + @Override + public void onRecordDeleteReplicated(ODocument doc) { + incrementSchemaVersion(doc); + } + + private void incrementSchemaVersion(ODocument doc) { + OImmutableClass immutableClass = ODocumentInternal.getImmutableSchemaClass(doc); + if (immutableClass == null) + return; + + final String className = immutableClass.getName(); + + if (className.equalsIgnoreCase(OUser.CLASS_NAME) || className.equalsIgnoreCase(ORole.CLASS_NAME)) { + + final OSecurity scr = security.get(); + if (scr != null) + scr.incrementVersion(); + + if (Orient.instance().getSecurity() != null && database != null) + Orient.instance().getSecurity().securityRecordChange(database.getURL(), doc); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityUser.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityUser.java new file mode 100644 index 00000000000..93c111cd69f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSecurityUser.java @@ -0,0 +1,60 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.io.Serializable; +import java.util.Set; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 03/11/14 + */ +public interface OSecurityUser extends Serializable { + enum STATUSES { + SUSPENDED, ACTIVE + } + + OSecurityRole allow(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation); + + OSecurityRole checkIfAllowed(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation); + + boolean isRuleDefined(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific); + + @Deprecated + OSecurityRole allow(final String iResource, final int iOperation); + + @Deprecated + OSecurityRole checkIfAllowed(final String iResource, final int iOperation); + + @Deprecated + boolean isRuleDefined(final String iResource); + + boolean checkPassword(final String iPassword); + + String getName(); + + OSecurityUser setName(final String iName); + + String getPassword(); + + OSecurityUser setPassword(final String iPassword); + + OSecurityUser.STATUSES getAccountStatus(); + + void setAccountStatus(OSecurityUser.STATUSES accountStatus); + + Set getRoles(); + + OSecurityUser addRole(final String iRole); + + OSecurityUser addRole(final OSecurityRole iRole); + + boolean removeRole(final String iRoleName); + + boolean hasRole(final String iRoleName, final boolean iIncludeInherited); + + OIdentifiable getIdentity(); + + ODocument getDocument(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemRole.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemRole.java new file mode 100644 index 00000000000..5fd906cc61a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemRole.java @@ -0,0 +1,63 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.annotation.OBeforeDeserialization; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.List; + +/** + */ +public class OSystemRole extends ORole { + public static final String DB_FILTER = "dbFilter"; + + private List dbFilter; + public List getDbFilter() { return dbFilter; } + + /** + * Constructor used in unmarshalling. + */ + public OSystemRole() { + } + + public OSystemRole(final String iName, final ORole iParent, final ALLOW_MODES iAllowMode) { + super(iName, iParent, iAllowMode); + } + + /** + * Create the role by reading the source document. + */ + public OSystemRole(final ODocument iSource) { + super(iSource); + } + + @Override + @OBeforeDeserialization + public void fromStream(final ODocument iSource) { + super.fromStream(iSource); + + if (document != null && document.containsField(DB_FILTER) && document.fieldType(DB_FILTER) == OType.EMBEDDEDLIST) { + dbFilter = document.field(DB_FILTER, OType.EMBEDDEDLIST); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemUser.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemUser.java new file mode 100644 index 00000000000..7ac7873cb5b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OSystemUser.java @@ -0,0 +1,92 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.List; + +/** + */ +public class OSystemUser extends OUser { + private String databaseName; + protected String getDatabaseName() { return databaseName; } + + /** + * Constructor used in unmarshalling. + */ + public OSystemUser() { + } + + public OSystemUser(final String iName) { + super(iName); + } + + public OSystemUser(String iUserName, final String iUserPassword) { + super(iUserName, iUserPassword); + } + + /** + * Create the user by reading the source document. + */ + public OSystemUser(final ODocument iSource) { + super(iSource); + } + + /** + * dbName is the name of the source database and is used for filtering roles. + */ + public OSystemUser(final ODocument iSource, final String dbName) { + databaseName = dbName; + fromStream(iSource); + } + + /** + * Derived classes can override createRole() to return an extended ORole implementation. + */ + protected ORole createRole(final ODocument roleDoc) { + ORole role = null; + + // If databaseName is set, then only allow roles with the same databaseName. + if (databaseName != null && !databaseName.isEmpty()) { + if (roleDoc != null && roleDoc.containsField(OSystemRole.DB_FILTER) && roleDoc.fieldType(OSystemRole.DB_FILTER) == OType.EMBEDDEDLIST) { + + List dbNames = roleDoc.field(OSystemRole.DB_FILTER, OType.EMBEDDEDLIST); + + for (String dbName : dbNames) { + if (dbName != null && !dbName.isEmpty() && (dbName.equalsIgnoreCase(databaseName) || dbName.equals("*"))) { + role = new OSystemRole(roleDoc); + break; + } + } + } + } + // If databaseName is not set, only return roles without a OSystemRole.DB_FILTER property. + else { + if (roleDoc != null && !roleDoc.containsField(OSystemRole.DB_FILTER)) { + role = new OSystemRole(roleDoc); + } + } + + return role; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OToken.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OToken.java new file mode 100644 index 00000000000..14c1ff9c936 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OToken.java @@ -0,0 +1,36 @@ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.id.ORID; + +/** + * Created by emrul on 25/10/2014. + * + * @author Emrul Islam Copyright 2014 Emrul Islam + */ +public interface OToken { + + boolean getIsVerified(); + + void setIsVerified(boolean verified); + + boolean getIsValid(); + + void setIsValid(boolean valid); + + String getUserName(); + + OUser getUser(ODatabaseDocumentInternal db); + + String getDatabase(); + + String getDatabaseType(); + + ORID getUserId(); + + long getExpiry(); + + void setExpiry(long expiry); + + boolean isNowValid(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OTokenException.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OTokenException.java new file mode 100644 index 00000000000..9ef599f1c57 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OTokenException.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import java.io.IOException; + +public class OTokenException extends IOException { + + private static final long serialVersionUID = -3003977236203691448L; + + public OTokenException(OTokenException exception) { + super(exception); + } + + public OTokenException(String string) { + super(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUser.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUser.java new file mode 100644 index 00000000000..cafa6ded3ce --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUser.java @@ -0,0 +1,313 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.annotation.OAfterDeserialization; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.OSecurityManager; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Contains the user settings about security and permissions. Each user has one or more roles associated. Roles contains the + * permission rules that define what the user can access and what he cannot. + * + * @author Luca Garulli + * + * @see ORole + */ +public class OUser extends OIdentity implements OSecurityUser { + public static final String ADMIN = "admin"; + public static final String CLASS_NAME = "OUser"; + public static final String PASSWORD_FIELD = "password"; + + private static final long serialVersionUID = 1L; + + // AVOID THE INVOCATION OF SETTER + protected Set roles = new HashSet(); + + /** + * Constructor used in unmarshalling. + */ + public OUser() { + } + + public OUser(final String iName) { + super(CLASS_NAME); + document.field("name", iName); + setAccountStatus(STATUSES.ACTIVE); + } + + public OUser(String iUserName, final String iUserPassword) { + super("OUser"); + document.field("name", iUserName); + setPassword(iUserPassword); + setAccountStatus(STATUSES.ACTIVE); + } + + /** + * Create the user by reading the source document. + */ + public OUser(final ODocument iSource) { + fromStream(iSource); + } + + public static final String encryptPassword(final String iPassword) { + return OSecurityManager.instance().createHash(iPassword, OGlobalConfiguration.SECURITY_USER_PASSWORD_DEFAULT_ALGORITHM.getValueAsString(), true); + } + + @Override + @OAfterDeserialization + public void fromStream(final ODocument iSource) { + if (document != null) + return; + + document = iSource; + + roles = new HashSet(); + final Collection loadedRoles = iSource.field("roles"); + if (loadedRoles != null) + for (final ODocument d : loadedRoles) { + if (d != null) { + ORole role = createRole(d); + if(role != null) roles.add(role); + } else + OLogManager.instance() + .warn(this, "User '%s' is declared to have a role that does not exist in the database. Ignoring it.", getName()); + + } + } + + /** + * Derived classes can override createRole() to return an extended ORole implementation or + * null if the role should not be added. + */ + protected ORole createRole(final ODocument roleDoc) { + return new ORole(roleDoc); + } + + /** + * Checks if the user has the permission to access to the requested resource for the requested operation. + * + * @param iOperation + * Requested operation + * @return The role that has granted the permission if any, otherwise a OSecurityAccessException exception is raised + * @exception OSecurityAccessException + */ + public ORole allow(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + if (roles == null || roles.isEmpty()) { + if (document.field("roles") != null && !((Collection) document.field("roles")).isEmpty()) { + final ODocument doc = document; + document = null; + fromStream(doc); + } else + throw new OSecurityAccessException(document.getDatabase().getName(), "User '" + document.field("name") + + "' has no role defined"); + } + + final ORole role = checkIfAllowed(resourceGeneric, resourceSpecific, iOperation); + + if (role == null) + throw new OSecurityAccessException(document.getDatabase().getName(), "User '" + document.field("name") + + "' does not have permission to execute the operation '" + ORole.permissionToString(iOperation) + + "' against the resource: " + resourceGeneric + "." + resourceSpecific); + + return role; + } + + /** + * Checks if the user has the permission to access to the requested resource for the requested operation. + * + * @param iOperation + * Requested operation + * @return The role that has granted the permission if any, otherwise null + */ + public ORole checkIfAllowed(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) { + for (ORole r : roles) { + if (r == null) + OLogManager.instance().warn(this, + "User '%s' has a null role, ignoring it. Consider fixing this user's roles before continuing", getName()); + else if (r.allow(resourceGeneric, resourceSpecific, iOperation)) + return r; + } + + return null; + } + + @Override + @Deprecated + public OSecurityRole allow(String iResource, int iOperation) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return allow(resourceGeneric, null, iOperation); + + return allow(resourceGeneric, resourceSpecific, iOperation); + } + + @Override + @Deprecated + public OSecurityRole checkIfAllowed(String iResource, int iOperation) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return checkIfAllowed(resourceGeneric, null, iOperation); + + return checkIfAllowed(resourceGeneric, resourceSpecific, iOperation); + } + + @Override + @Deprecated + public boolean isRuleDefined(String iResource) { + final String resourceSpecific = ORule.mapLegacyResourceToSpecificResource(iResource); + final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource); + + if (resourceSpecific == null || resourceSpecific.equals("*")) + return isRuleDefined(resourceGeneric, null); + + return isRuleDefined(resourceGeneric, resourceSpecific); + } + + /** + * Checks if a rule was defined for the user. + * + * @return True is a rule is defined, otherwise false + */ + public boolean isRuleDefined(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific) { + for (ORole r : roles) + if (r == null) + OLogManager.instance().warn(this, + "User '%s' has a null role, bypass it. Consider to fix this user roles before to continue", getName()); + else if (r.hasRule(resourceGeneric, resourceSpecific)) + return true; + + return false; + } + + public boolean checkPassword(final String iPassword) { + return OSecurityManager.instance().checkPassword(iPassword, (String) document.field(PASSWORD_FIELD)); + } + + public String getName() { + return document.field("name"); + } + + public OUser setName(final String iName) { + document.field("name", iName); + return this; + } + + public String getPassword() { + return document.field(PASSWORD_FIELD); + } + + public OUser setPassword(final String iPassword) { + document.field(PASSWORD_FIELD, iPassword); + return this; + } + + public STATUSES getAccountStatus() { + final String status = (String) document.field("status"); + if (status == null) + throw new OSecurityException("User '" + getName() + "' has no status"); + return STATUSES.valueOf(status); + } + + public void setAccountStatus(STATUSES accountStatus) { + document.field("status", accountStatus); + } + + public Set getRoles() { + return roles; + } + + public OUser addRole(final String iRole) { + if (iRole != null) + addRole(document.getDatabase().getMetadata().getSecurity().getRole(iRole)); + return this; + } + + public OUser addRole(final OSecurityRole iRole) { + if (iRole != null) + roles.add((ORole) iRole); + + final HashSet persistentRoles = new HashSet(); + for (ORole r : roles) { + persistentRoles.add(r.toStream()); + } + document.field("roles", persistentRoles); + return this; + } + + public boolean removeRole(final String iRoleName) { + for (Iterator it = roles.iterator(); it.hasNext();) + if (it.next().getName().equals(iRoleName)) { + it.remove(); + return true; + } + return false; + } + + public boolean hasRole(final String iRoleName, final boolean iIncludeInherited) { + for (Iterator it = roles.iterator(); it.hasNext();) { + final ORole role = it.next(); + if (role.getName().equals(iRoleName)) + return true; + + if (iIncludeInherited) { + ORole r = role.getParentRole(); + while (r != null) { + if (r.getName().equals(iRoleName)) + return true; + r = r.getParentRole(); + } + } + } + + return false; + } + + @Override + @SuppressWarnings("unchecked") + public OUser save() { + document.save(OUser.class.getSimpleName()); + return this; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public OIdentifiable getIdentity() { + return document; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUserTrigger.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUserTrigger.java new file mode 100644 index 00000000000..1a00c89038a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/OUserTrigger.java @@ -0,0 +1,100 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.metadata.security; + +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; + +/** + * Encrypt the password using the SHA-256 algorithm. + * + * @author Luca Garulli + */ +public class OUserTrigger extends ODocumentHookAbstract implements ORecordHook.Scoped { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE }; + + public OUserTrigger(ODatabaseDocument database) { + super(database); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + @Override + public RESULT onTrigger(TYPE iType, ORecord iRecord) { + OImmutableClass clazz = null; + if (iRecord instanceof ODocument) + clazz = ODocumentInternal.getImmutableSchemaClass((ODocument) iRecord); + if (clazz == null || (!clazz.isOuser() && !clazz.isOrole())) + return RESULT.RECORD_NOT_CHANGED; + return super.onTrigger(iType, iRecord); + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE; + } + + @Override + public RESULT onRecordBeforeCreate(final ODocument iDocument) { + if (ODocumentInternal.getImmutableSchemaClass(iDocument).isOuser()) + return encodePassword(iDocument); + + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + if (ODocumentInternal.getImmutableSchemaClass(iDocument).isOuser()) + return encodePassword(iDocument); + + return RESULT.RECORD_NOT_CHANGED; + } + + private RESULT encodePassword(final ODocument iDocument) { + if (iDocument.field("name") == null) + throw new OSecurityException("User name not found"); + + final String password = (String) iDocument.field("password"); + + if (password == null) + throw new OSecurityException("User '" + iDocument.field("name") + "' has no password"); + + if (Orient.instance().getSecurity() != null) { + Orient.instance().getSecurity().validatePassword(password); + } + + if (!password.startsWith("{")) { + iDocument.field("password", OUser.encryptPassword(password)); + return RESULT.RECORD_CHANGED; + } + + return RESULT.RECORD_NOT_CHANGED; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJsonWebToken.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJsonWebToken.java new file mode 100644 index 00000000000..bff1b4d662d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJsonWebToken.java @@ -0,0 +1,14 @@ +package com.orientechnologies.orient.core.metadata.security.jwt; + +/** + * Created by emrul on 28/09/2014. + * + * @author Emrul Islam + * Copyright 2014 Emrul Islam + */ +public interface OJsonWebToken { + + public OJwtHeader getHeader(); + + public OJwtPayload getPayload(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtHeader.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtHeader.java new file mode 100644 index 00000000000..03dbf7337d0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtHeader.java @@ -0,0 +1,24 @@ +package com.orientechnologies.orient.core.metadata.security.jwt; + + +/** + * Created by emrul on 28/09/2014. + * + * @author Emrul Islam + * Copyright 2014 Emrul Islam + */ +public interface OJwtHeader { + + public String getAlgorithm(); + + public void setAlgorithm(String alg); + + public String getType(); + + public void setType(String typ); + + public String getKeyId(); + + public void setKeyId(String kid); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtPayload.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtPayload.java new file mode 100644 index 00000000000..78835d5d170 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OJwtPayload.java @@ -0,0 +1,45 @@ +package com.orientechnologies.orient.core.metadata.security.jwt; + +/** + * Created by emrul on 28/09/2014. + * + * @author Emrul Islam Copyright 2014 Emrul Islam + */ +public interface OJwtPayload { + + public String getIssuer(); + + public void setIssuer(String iss); + + public long getExpiry(); + + public void setExpiry(long exp); + + public long getIssuedAt(); + + public void setIssuedAt(long iat); + + public long getNotBefore(); + + public void setNotBefore(long nbf); + + public String getUserName(); + + public void setUserName(String sub); + + public String getAudience(); + + public void setAudience(String aud); + + public String getTokenId(); + + public void setTokenId(String jti); + + public void setDatabase(String database); + + public String getDatabase(); + + public void setDatabaseType(String databaseType); + + public String getDatabaseType(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OKeyProvider.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OKeyProvider.java new file mode 100644 index 00000000000..668bef28957 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/security/jwt/OKeyProvider.java @@ -0,0 +1,17 @@ +package com.orientechnologies.orient.core.metadata.security.jwt; + +import java.security.Key; + +/** + * Created by emrul on 28/09/2014. + * + * @author Emrul Islam Copyright 2014 Emrul Islam + */ +public interface OKeyProvider { + + public Key getKey(OJwtHeader header); + + public String[] getKeys(); + + public String getDefaultKey(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequence.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequence.java new file mode 100755 index 00000000000..c3a062ed708 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequence.java @@ -0,0 +1,305 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import java.util.Random; +import java.util.concurrent.Callable; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.util.OApi; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.exception.OSequenceException; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/2/2015 + */ +public abstract class OSequence { + public static final long DEFAULT_START = 0; + public static final int DEFAULT_INCREMENT = 1; + public static final int DEFAULT_CACHE = 20; + + protected static final int DEF_MAX_RETRY = OGlobalConfiguration.SEQUENCE_MAX_RETRY.getValueAsInteger(); + public static final String CLASS_NAME = "OSequence"; + + private static final String FIELD_START = "start"; + private static final String FIELD_INCREMENT = "incr"; + private static final String FIELD_VALUE = "value"; + + private static final String FIELD_NAME = "name"; + private static final String FIELD_TYPE = "type"; + + private ODocument document; + private ThreadLocal tlDocument = new ThreadLocal(); + + public static class CreateParams { + public Long start = DEFAULT_START; + public Integer increment = DEFAULT_INCREMENT; + public Integer cacheSize = DEFAULT_CACHE; + + public CreateParams setStart(Long start) { + this.start = start; + return this; + } + + public CreateParams setIncrement(Integer increment) { + this.increment = increment; + return this; + } + + public CreateParams setCacheSize(Integer cacheSize) { + this.cacheSize = cacheSize; + return this; + } + + public CreateParams() { + } + + public CreateParams setDefaults() { + this.start = this.start != null ? this.start : DEFAULT_START; + this.increment = this.increment != null ? this.increment : DEFAULT_INCREMENT; + this.cacheSize = this.cacheSize != null ? this.cacheSize : DEFAULT_CACHE; + + return this; + } + } + + public enum SEQUENCE_TYPE { + CACHED, ORDERED,; + } + + private int maxRetry = DEF_MAX_RETRY; + + protected OSequence() { + this(null, null); + } + + protected OSequence(final ODocument iDocument) { + this(iDocument, null); + } + + protected OSequence(final ODocument iDocument, CreateParams params) { + document = iDocument != null ? iDocument : new ODocument(CLASS_NAME); + bindOnLocalThread(); + + if (iDocument == null) { + if (params == null) { + params = new CreateParams().setDefaults(); + } + + initSequence(params); + + document = getDocument(); + } + } + + public void save() { + ODocument doc = tlDocument.get(); + doc.save(); + onUpdate(doc); + } + + void bindOnLocalThread() { + tlDocument.set(document.copy()); + } + + public ODocument getDocument() { + return tlDocument.get(); + } + + protected synchronized void initSequence(OSequence.CreateParams params) { + setStart(params.start); + setIncrement(params.increment); + setValue(params.start); + + setSequenceType(); + } + + public synchronized boolean updateParams(CreateParams params) { + boolean any = false; + + if (params.start != null && this.getStart() != params.start) { + this.setStart(params.start); + any = true; + } + + if (params.increment != null && this.getIncrement() != params.increment) { + this.setIncrement(params.increment); + any = true; + } + + return any; + } + + public void onUpdate(ODocument iDocument) { + document = iDocument; + this.tlDocument.set(iDocument); + } + + protected synchronized long getValue() { + return tlDocument.get().field(FIELD_VALUE, OType.LONG); + } + + protected synchronized void setValue(long value) { + tlDocument.get().field(FIELD_VALUE, value); + } + + protected synchronized int getIncrement() { + return tlDocument.get().field(FIELD_INCREMENT, OType.INTEGER); + } + + protected synchronized void setIncrement(int value) { + tlDocument.get().field(FIELD_INCREMENT, value); + } + + protected synchronized long getStart() { + return tlDocument.get().field(FIELD_START, OType.LONG); + } + + protected synchronized void setStart(long value) { + tlDocument.get().field(FIELD_START, value); + } + + public synchronized int getMaxRetry() { + return maxRetry; + } + + public synchronized void setMaxRetry(final int maxRetry) { + this.maxRetry = maxRetry; + } + + public synchronized String getName() { + return getSequenceName(tlDocument.get()); + } + + public synchronized OSequence setName(final String name) { + tlDocument.get().field(FIELD_NAME, name); + return this; + } + + private synchronized void setSequenceType() { + tlDocument.get().field(FIELD_TYPE, getSequenceType()); + } + + protected synchronized ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public static String getSequenceName(final ODocument iDocument) { + return iDocument.field(FIELD_NAME, OType.STRING); + } + + public static SEQUENCE_TYPE getSequenceType(final ODocument document) { + String sequenceTypeStr = document.field(FIELD_TYPE); + if (sequenceTypeStr != null) + return SEQUENCE_TYPE.valueOf(sequenceTypeStr); + + return null; + } + + public static void initClass(OClassImpl sequenceClass) { + sequenceClass.createProperty(OSequence.FIELD_START, OType.LONG, (OType) null, true); + sequenceClass.createProperty(OSequence.FIELD_INCREMENT, OType.INTEGER, (OType) null, true); + sequenceClass.createProperty(OSequence.FIELD_VALUE, OType.LONG, (OType) null, true); + + sequenceClass.createProperty(OSequence.FIELD_NAME, OType.STRING, (OType) null, true); + sequenceClass.createProperty(OSequence.FIELD_TYPE, OType.STRING, (OType) null, true); + } + + /* + * Forwards the sequence by one, and returns the new value. + */ + @OApi + public abstract long next(); + + /* + * Returns the current sequence value. If next() was never called, returns null + */ + @OApi + public abstract long current(); + + /* + * Resets the sequence value to it's initialized value. + */ + @OApi + public abstract long reset(); + + /* + * Returns the sequence type + */ + public abstract SEQUENCE_TYPE getSequenceType(); + + protected void checkForUpdateToLastversion() { + final ODocument tlDoc = tlDocument.get(); + if (tlDoc != null) { + if (document.getVersion() > tlDoc.getVersion()) + tlDocument.set(document); + } + } + + protected void reloadSequence() { + tlDocument.set(tlDocument.get().reload(null, true)); + } + + protected T callRetry(final Callable callable, final String method) { + for (int retry = 0; retry < maxRetry; ++retry) { + try { + return callable.call(); + } catch (OConcurrentModificationException ex) { + try { + Thread.sleep(1 + new Random().nextInt(OGlobalConfiguration.SEQUENCE_RETRY_DELAY.getValueAsInteger())); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + reloadSequence(); + } catch (OStorageException e) { + if (e.getCause() instanceof OConcurrentModificationException) { + reloadSequence(); + } else { + throw OException + .wrapException(new OSequenceException("Error in transactional processing of " + getName() + "." + method + "()"), e); + } + } catch (OException ex) { + reloadSequence(); + } catch (Exception e) { + throw OException + .wrapException(new OSequenceException("Error in transactional processing of " + getName() + "." + method + "()"), e); + } + } + + try { + return callable.call(); + } catch (Exception e) { + if (e.getCause() instanceof OConcurrentModificationException) { + throw ((OConcurrentModificationException) e.getCause()); + } + throw OException + .wrapException(new OSequenceException("Error in transactional processing of " + getName() + "." + method + "()"), e); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceCached.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceCached.java new file mode 100644 index 00000000000..dcd62cc98f7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceCached.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.concurrent.Callable; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/3/2015 + */ +public class OSequenceCached extends OSequence { + private static final String FIELD_CACHE = "cache"; + private long cacheStart = 0L; + private long cacheEnd = 0L; + + public OSequenceCached() { + super(); + } + + public OSequenceCached(final ODocument iDocument) { + super(iDocument); + } + + public OSequenceCached(final ODocument iDocument, OSequence.CreateParams params) { + super(iDocument, params); + } + + @Override + public boolean updateParams(OSequence.CreateParams params) { + boolean any = super.updateParams(params); + if (params.cacheSize != null && this.getCacheSize() != params.cacheSize) { + this.setCacheSize(params.cacheSize); + any = true; + } + return any; + } + + @Override + protected void initSequence(OSequence.CreateParams params) { + super.initSequence(params); + setCacheSize(params.cacheSize); + } + + @Override + public long next() { + return callRetry(new Callable() { + @Override + public Long call() throws Exception { + int increment = getIncrement(); + if (cacheStart + increment >= cacheEnd) { + allocateCache(getCacheSize()); + } + + cacheStart = cacheStart + increment; + return cacheStart; + } + }, "next"); + } + + @Override + public long current() { + return this.cacheStart; + } + + @Override + public long reset() { + return callRetry(new Callable() { + @Override + public Long call() throws Exception { + long newValue = getStart(); + setValue(newValue); + save(); + + // + allocateCache(getCacheSize()); + + return newValue; + } + }, "reset"); + } + + @Override + public SEQUENCE_TYPE getSequenceType() { + return SEQUENCE_TYPE.CACHED; + } + + public int getCacheSize() { + return getDocument().field(FIELD_CACHE, OType.INTEGER); + } + + public void setCacheSize(int cacheSize) { + getDocument().field(FIELD_CACHE, cacheSize); + } + + private void allocateCache(int cacheSize) { + long value = getValue(); + long newValue = value + (getIncrement() * cacheSize); + setValue(newValue); + save(); + + this.cacheStart = value; + this.cacheEnd = newValue - 1; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceHelper.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceHelper.java new file mode 100644 index 00000000000..75cab9ab1e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceHelper.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/1/2015 + */ +public class OSequenceHelper { + public static final SEQUENCE_TYPE DEFAULT_SEQUENCE_TYPE = SEQUENCE_TYPE.CACHED; + + public static OSequence createSequence(SEQUENCE_TYPE sequenceType, OSequence.CreateParams params, ODocument document) { + switch (sequenceType) { + case ORDERED: + return new OSequenceOrdered(document, params); + case CACHED: + return new OSequenceCached(document, params); + default: + throw new IllegalArgumentException("sequenceType"); + } + } + + public static SEQUENCE_TYPE getSequenceTyeFromString(String typeAsString) { + return SEQUENCE_TYPE.valueOf(typeAsString); + } + + public static OSequence createSequence(ODocument document) { + SEQUENCE_TYPE sequenceType = OSequence.getSequenceType(document); + if (sequenceType != null) + return createSequence(sequenceType, null, document); + + return null; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibrary.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibrary.java new file mode 100644 index 00000000000..fc0d5822045 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibrary.java @@ -0,0 +1,53 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Set; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/2/2015 + */ +public interface OSequenceLibrary { + Set getSequenceNames(); + + int getSequenceCount(); + + OSequence createSequence(String iName, SEQUENCE_TYPE sequenceType, OSequence.CreateParams params); + + OSequence getSequence(String iName); + + void dropSequence(String iName); + + OSequence onSequenceCreated(ODocument iDocument); + + OSequence onSequenceUpdated(ODocument iDocument); + + void onSequenceDropped(ODocument iDocument); + + void create(); + + void load(); + + void close(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryImpl.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryImpl.java new file mode 100644 index 00000000000..2e7eb29e470 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryImpl.java @@ -0,0 +1,195 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ + +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OSequenceException; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/2/2015 + */ +public class OSequenceLibraryImpl implements OSequenceLibrary { + private final Map sequences = new ConcurrentHashMap(); + + @Override + public void create() { + init(); + } + + @Override + public void load() { + sequences.clear(); + + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot().existsClass(OSequence.CLASS_NAME)) { + final List result = db.query(new OSQLSynchQuery("SELECT FROM " + OSequence.CLASS_NAME)); + for (ODocument document : result) { + document.reload(); + + final OSequence sequence = OSequenceHelper.createSequence(document); + if (sequence != null) { + sequences.put(sequence.getName().toUpperCase(Locale.ENGLISH), sequence); + } + } + } + } + + @Override + public void close() { + sequences.clear(); + } + + @Override + public Set getSequenceNames() { + return sequences.keySet(); + } + + @Override + public int getSequenceCount() { + return sequences.size(); + } + + @Override + public OSequence getSequence(String iName) { + final String name = iName.toUpperCase(Locale.ENGLISH); + + OSequence seq = sequences.get(name); + if (seq == null) { + load(); + seq = sequences.get(name); + } + + if (seq != null) { + seq.bindOnLocalThread(); + seq.checkForUpdateToLastversion(); + } + + return seq; + } + + @Override + public OSequence createSequence(final String iName, final SEQUENCE_TYPE sequenceType, final OSequence.CreateParams params) { + init(); + + final String key = iName.toUpperCase(Locale.ENGLISH); + validateSequenceNoExists(key); + + final OSequence sequence = OSequenceHelper.createSequence(sequenceType, params, null).setName(iName); + sequence.save(); + sequences.put(key, sequence); + + return sequence; + } + + @Override + public void dropSequence(final String iName) { + final OSequence seq = getSequence(iName); + + if (seq != null) { + ODatabaseRecordThreadLocal.INSTANCE.get().delete(seq.getDocument().getIdentity()); + sequences.remove(iName.toUpperCase(Locale.ENGLISH)); + } + } + + @Override + public OSequence onSequenceCreated(final ODocument iDocument) { + init(); + + String name = OSequence.getSequenceName(iDocument); + if (name == null) + return null; + + name = name.toUpperCase(Locale.ENGLISH); + + final OSequence seq = getSequence(name); + + if (seq != null) + return seq; + + final OSequence sequence = OSequenceHelper.createSequence(iDocument); + + sequences.put(name, sequence); + return sequence; + } + + @Override + public OSequence onSequenceUpdated(final ODocument iDocument) { + String name = OSequence.getSequenceName(iDocument); + if (name == null) + return null; + + name = name.toUpperCase(Locale.ENGLISH); + + final OSequence sequence = sequences.get(name); + if (sequence == null) + return null; + + sequence.onUpdate(iDocument); + + return sequence; + } + + @Override + public void onSequenceDropped(final ODocument iDocument) { + String name = OSequence.getSequenceName(iDocument); + if (name == null) + return; + + name = name.toUpperCase(Locale.ENGLISH); + + sequences.remove(name); + } + + private void init() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (db.getMetadata().getSchema().existsClass(OSequence.CLASS_NAME)) { + return; + } + + final OClassImpl sequenceClass = (OClassImpl) db.getMetadata().getSchema().createClass(OSequence.CLASS_NAME); + OSequence.initClass(sequenceClass); + } + + private void validateSequenceNoExists(final String iName) { + if (sequences.containsKey(iName)) { + throw new OSequenceException("Sequence '" + iName + "' already exists"); + } + } + + private void validateSequenceExists(final String iName) { + if (!sequences.containsKey(iName)) { + throw new OSequenceException("Sequence '" + iName + "' does not exists"); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryProxy.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryProxy.java new file mode 100644 index 00000000000..bc47995a049 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceLibraryProxy.java @@ -0,0 +1,92 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OProxedResource; +import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Set; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/2/2015 + */ +public class OSequenceLibraryProxy extends OProxedResource implements OSequenceLibrary { + public OSequenceLibraryProxy(final OSequenceLibrary iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + @Override + public Set getSequenceNames() { + return delegate.getSequenceNames(); + } + + @Override + public int getSequenceCount() { + return delegate.getSequenceCount(); + } + + @Override + public OSequence getSequence(String iName) { + return delegate.getSequence(iName); + } + + @Override + public OSequence createSequence(String iName, SEQUENCE_TYPE sequenceType, OSequence.CreateParams params) { + return delegate.createSequence(iName, sequenceType, params); + } + + @Override + public void dropSequence(String iName) { + delegate.dropSequence(iName); + } + + @Override + public OSequence onSequenceCreated(ODocument iDocument) { + return delegate.onSequenceCreated(iDocument); + } + + @Override + public OSequence onSequenceUpdated(ODocument iDocument) { + return delegate.onSequenceUpdated(iDocument); + } + + @Override + public void onSequenceDropped(ODocument iDocument) { + delegate.onSequenceDropped(iDocument); + } + + @Override + public void create() { + delegate.create(); + } + + @Override + public void load() { + delegate.load(); + } + + @Override + public void close() { + delegate.close(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceOrdered.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceOrdered.java new file mode 100644 index 00000000000..6610e7fbd7b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceOrdered.java @@ -0,0 +1,85 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.concurrent.Callable; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 2/28/2015 + * + * A sequence with sequential guarantees. Even when a transaction is rolled back, + * there will still be no holes. However, as a result, it is slower. + * @see OSequenceCached + */ +public class OSequenceOrdered extends OSequence { + public OSequenceOrdered() { + super(); + } + + public OSequenceOrdered(final ODocument iDocument) { + super(iDocument); + } + + public OSequenceOrdered(final ODocument iDocument, OSequence.CreateParams params) { + super(iDocument, params); + } + + @Override + public synchronized long next() { + return callRetry(new Callable() { + @Override + public Long call() throws Exception { + long newValue = getValue() + getIncrement(); + setValue(newValue); + + save(); + + return newValue; + } + }, "next"); + } + + @Override + public synchronized long current() { + return getValue(); + } + + @Override + public synchronized long reset() { + return callRetry(new Callable() { + @Override + public Long call() throws Exception { + long newValue = getStart(); + setValue(newValue); + save(); + + return newValue; + } + }, "reset"); + } + + @Override + public SEQUENCE_TYPE getSequenceType() { + return SEQUENCE_TYPE.ORDERED; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceTrigger.java b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceTrigger.java new file mode 100644 index 00000000000..d40cc6aed2d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/metadata/sequence/OSequenceTrigger.java @@ -0,0 +1,77 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.metadata.sequence; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Update the in-memory function library. + * + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/2/2015 + */ +public class OSequenceTrigger extends ODocumentHookAbstract implements ORecordHook.Scoped { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + public OSequenceTrigger(ODatabaseDocument database) { + super(database); + setIncludeClasses(OSequence.CLASS_NAME); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + @Override + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE; + } + + @Override + public void onRecordAfterCreate(final ODocument iDocument) { + getSequenceLibrary().onSequenceCreated(iDocument); + } + + private static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + @Override + public void onRecordAfterUpdate(final ODocument iDocument) { + getSequenceLibrary().onSequenceUpdated(iDocument); + } + + @Override + public void onRecordAfterDelete(final ODocument iDocument) { + getSequenceLibrary().onSequenceDropped(iDocument); + } + + private OSequenceLibrary getSequenceLibrary() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + return db.getMetadata().getSequenceLibrary(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/package.html b/core/src/main/java/com/orientechnologies/orient/core/package.html new file mode 100644 index 00000000000..bc1536add00 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/package.html @@ -0,0 +1,24 @@ + + + + +Core package. + diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/OQuery.java b/core/src/main/java/com/orientechnologies/orient/core/query/OQuery.java new file mode 100644 index 00000000000..4142c83ec16 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/OQuery.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query; + +import java.util.List; + +import com.orientechnologies.orient.core.command.OCommandRequest; + +public interface OQuery extends OCommandRequest { + + /** + * Executes the query without limit about the result set. The limit will be bound to the maximum allowed. + * + * @return List of records if any record matches the query constraints, otherwise an empty List. + */ + public List run(Object... iArgs); + + /** + * Returns the first occurrence found if any + * + * @return Record if found, otherwise null + */ + public T runFirst(Object... iArgs); + + public void reset(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/OQueryAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryAbstract.java new file mode 100644 index 00000000000..2a9a4886d81 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryAbstract.java @@ -0,0 +1,67 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query; + +import com.orientechnologies.orient.core.command.OCommandRequestAbstract; +import com.orientechnologies.orient.core.fetch.OFetchHelper; + +@SuppressWarnings("serial") +public abstract class OQueryAbstract extends OCommandRequestAbstract implements OQuery { + public OQueryAbstract() { + useCache = true; + } + + @SuppressWarnings("unchecked") + public RET execute(final Object... iArgs) { + return (RET) run(iArgs); + } + + /** + * Returns the current fetch plan. + */ + public String getFetchPlan() { + return fetchPlan; + } + + /** + * Sets the fetch plan to use. + */ + public OQueryAbstract setFetchPlan(final String fetchPlan) { + OFetchHelper.checkFetchPlanValid(fetchPlan); + if (fetchPlan != null && fetchPlan.length() == 0) + this.fetchPlan = null; + else + this.fetchPlan = fetchPlan; + return this; + } + + /** + * Resets the query removing the result set. Call this to reuse the Query object preventing a pagination. + */ + @Override + public void reset() { + } + + @Override + public boolean isIdempotent() { + return true; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/OQueryContext.java b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryContext.java new file mode 100644 index 00000000000..05533474bf6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryContext.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class OQueryContext { + protected ODocument initialRecord; + protected OQuery sourceQuery; + + public void setRecord(final ODocument iRecord) { + this.initialRecord = iRecord; + } + + public void setSourceQuery(final OQuery sourceQuery) { + this.sourceQuery = sourceQuery; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/OQueryHelper.java b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryHelper.java new file mode 100644 index 00000000000..f913f3b6a8a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryHelper.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query; + +public class OQueryHelper { + protected static final String WILDCARD_ANYCHAR = "?"; + protected static final String WILDCARD_ANY = "%"; + + public static boolean like(final String currentValue, String iValue) { + if (currentValue == null || currentValue.length() == 0 || iValue == null || iValue.length() == 0) + // EMPTY/NULL PARAMETERS + return false; + + final int anyPos = iValue.indexOf(WILDCARD_ANY); + final int charAnyPos = iValue.indexOf(WILDCARD_ANYCHAR); + + if (anyPos == -1 && charAnyPos == -1) + // NO WILDCARDS: DO EQUALS + return currentValue.equals(iValue); + + final String value = currentValue.toString(); + if (value == null || value.length() == 0) + // NOTHING TO MATCH + return false; + + if (iValue.startsWith(WILDCARD_ANY) && iValue.endsWith(WILDCARD_ANY)) { + // %XXXXX% + iValue = iValue.substring(WILDCARD_ANY.length(), iValue.length() - WILDCARD_ANY.length()); + return currentValue.indexOf(iValue) > -1; + + } else if (iValue.startsWith(WILDCARD_ANY)) { + // %XXXXX + iValue = iValue.substring(WILDCARD_ANY.length()); + return value.endsWith(iValue); + + } else if (iValue.endsWith(WILDCARD_ANY)) { + // XXXXX% + iValue = iValue.substring(0, iValue.length() - WILDCARD_ANY.length()); + return value.startsWith(iValue); + + } else { + final int pos = iValue.indexOf(WILDCARD_ANY); + if (pos > -1) { + // XX%XXX + return value.startsWith(iValue.substring(0, pos)) && value.endsWith(iValue.substring(pos + 1)); + } + } + + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/OQueryRuntimeValueMulti.java b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryRuntimeValueMulti.java new file mode 100755 index 00000000000..008fdd27700 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/OQueryRuntimeValueMulti.java @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemFieldMultiAbstract; + +import java.util.List; + +/** + * Represent multiple values in query. + * + * @author Luca Garulli + * + */ +public class OQueryRuntimeValueMulti { + protected final OSQLFilterItemFieldMultiAbstract definition; + protected final List collates; + protected final Object[] values; + + public OQueryRuntimeValueMulti(final OSQLFilterItemFieldMultiAbstract iDefinition, final Object[] iValues, + final List iCollates) { + definition = iDefinition; + values = iValues; + collates = iCollates; + } + + @Override + public String toString() { + if (getValues() == null) + return ""; + + StringBuilder buffer = new StringBuilder(128); + buffer.append("["); + int i = 0; + for (Object v : getValues()) { + if (i++ > 0) + buffer.append(","); + buffer.append(v); + } + buffer.append("]"); + return buffer.toString(); + } + + public OSQLFilterItemFieldMultiAbstract getDefinition() { + return definition; + } + + public OCollate getCollate(final int iIndex) { + return collates.get(iIndex); + } + + public Object[] getValues() { + return values; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryHook.java b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryHook.java new file mode 100644 index 00000000000..3c463d3979c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryHook.java @@ -0,0 +1,260 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query.live; + +import com.orientechnologies.common.concur.resource.OCloseable; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.ODatabaseListener; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by luigidellaquila on 16/03/15. + */ +public class OLiveQueryHook extends ODocumentHookAbstract implements ORecordHook.Scoped, ODatabaseListener { + + public static class OLiveQueryOps implements OCloseable { + + protected Map> pendingOps = new ConcurrentHashMap>(); + private OLiveQueryQueueThread queueThread = new OLiveQueryQueueThread(); + private Object threadLock = new Object(); + + @Override + public void close() { + queueThread.stopExecution(); + try { + queueThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + pendingOps.clear(); + } + + public OLiveQueryQueueThread getQueueThread() { + return queueThread; + } + } + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + public OLiveQueryHook(ODatabaseDocumentInternal db) { + super(db); + getOpsReference(db); + db.registerListener(this); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + public static OLiveQueryOps getOpsReference(ODatabaseInternal db) { + return (OLiveQueryOps) db.getStorage().getResource("LiveQueryOps", new Callable() { + @Override + public Object call() throws Exception { + return new OLiveQueryOps(); + } + }); + } + + public static Integer subscribe(Integer token, OLiveQueryListener iListener, ODatabaseInternal db) { + if (Boolean.FALSE.equals(db.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) { + OLogManager.instance().warn(db, + "Live query support is disabled impossible to subscribe a listener, set '%s' to true for enable the live query support", + OGlobalConfiguration.QUERY_LIVE_SUPPORT.getKey()); + return -1; + } + OLiveQueryOps ops = getOpsReference(db); + synchronized (ops.threadLock) { + if (!ops.queueThread.isAlive()) { + ops.queueThread = ops.queueThread.clone(); + ops.queueThread.start(); + } + } + + return ops.queueThread.subscribe(token, iListener); + } + + public static void unsubscribe(Integer id, ODatabaseInternal db) { + if (Boolean.FALSE.equals(db.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) { + OLogManager.instance().warn(db, + "Live query support is disabled impossible to unsubscribe a listener, set '%s' to true for enable the live query support", + OGlobalConfiguration.QUERY_LIVE_SUPPORT.getKey()); + return; + } + try { + OLiveQueryOps ops = getOpsReference(db); + synchronized (ops.threadLock) { + ops.queueThread.unsubscribe(id); + } + } catch (Exception e) { + OLogManager.instance().warn(OLiveQueryHook.class, "Error on unsubscribing client"); + } + } + + @Override + public void onCreate(ODatabase iDatabase) { + + } + + @Override + public void onDelete(ODatabase iDatabase) { + if (Boolean.FALSE.equals(database.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) + return; + OLiveQueryOps ops = getOpsReference((ODatabaseInternal) iDatabase); + synchronized (ops.pendingOps) { + ops.pendingOps.remove(iDatabase); + } + } + + @Override + public void onOpen(ODatabase iDatabase) { + + } + + @Override + public void onBeforeTxBegin(ODatabase iDatabase) { + + } + + @Override + public void onBeforeTxRollback(ODatabase iDatabase) { + + } + + @Override + public void onAfterTxRollback(ODatabase iDatabase) { + if (Boolean.FALSE.equals(database.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) + return; + OLiveQueryOps ops = getOpsReference((ODatabaseInternal) iDatabase); + synchronized (ops.pendingOps) { + ops.pendingOps.remove(iDatabase); + } + } + + @Override + public void onBeforeTxCommit(ODatabase iDatabase) { + + } + + @Override + public void onAfterTxCommit(ODatabase iDatabase) { + if (Boolean.FALSE.equals(database.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) + return; + OLiveQueryOps ops = getOpsReference((ODatabaseInternal) iDatabase); + List list; + synchronized (ops.pendingOps) { + list = ops.pendingOps.remove(iDatabase); + } + // TODO sync + if (list != null) { + for (ORecordOperation item : list) { + item.setRecord(item.getRecord().copy()); + ops.queueThread.enqueue(item); + } + } + } + + @Override + public void onClose(ODatabase iDatabase) { + if (Boolean.FALSE.equals(database.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) + return; + OLiveQueryOps ops = getOpsReference((ODatabaseInternal) iDatabase); + synchronized (ops.pendingOps) { + ops.pendingOps.remove(iDatabase); + } + } + + @Override + public void onBeforeCommand(OCommandRequestText iCommand, OCommandExecutor executor) { + + } + + @Override + public void onAfterCommand(OCommandRequestText iCommand, OCommandExecutor executor, Object result) { + + } + + @Override + public void onRecordAfterCreate(ODocument iDocument) { + addOp(iDocument, ORecordOperation.CREATED); + } + + @Override + public void onRecordAfterUpdate(ODocument iDocument) { + addOp(iDocument, ORecordOperation.UPDATED); + } + + @Override + public RESULT onRecordBeforeDelete(ODocument iDocument) { + addOp(iDocument, ORecordOperation.DELETED); + return RESULT.RECORD_NOT_CHANGED; + } + + protected void addOp(ODocument iDocument, byte iType) { + if (Boolean.FALSE.equals(database.getConfiguration().getValue(OGlobalConfiguration.QUERY_LIVE_SUPPORT))) + return; + ODatabaseDocument db = database; + OLiveQueryOps ops = getOpsReference((ODatabaseInternal) db); + if (!ops.queueThread.hasListeners()) + return; + if (db.getTransaction() == null || !db.getTransaction().isActive()) { + + // TODO synchronize + ORecordOperation op = new ORecordOperation(iDocument.copy(), iType); + ops.queueThread.enqueue(op); + return; + } + ORecordOperation result = new ORecordOperation(iDocument, iType); + synchronized (ops.pendingOps) { + List list = ops.pendingOps.get(db); + if (list == null) { + list = new ArrayList(); + ops.pendingOps.put(db, list); + } + list.add(result); + } + } + + @Override + public boolean onCorruptionRepairDatabase(ODatabase iDatabase, String iReason, String iWhatWillbeFixed) { + return false; + } + + @Override + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryListener.java b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryListener.java new file mode 100644 index 00000000000..a12900ffb8f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryListener.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query.live; + +import com.orientechnologies.orient.core.db.record.ORecordOperation; + +/** + * Created by luigidellaquila on 16/03/15. + */ +public interface OLiveQueryListener { + + void onLiveResult(ORecordOperation iRecord); + + void onLiveResultEnd(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryQueueThread.java b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryQueueThread.java new file mode 100644 index 00000000000..75252df2cba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/query/live/OLiveQueryQueueThread.java @@ -0,0 +1,106 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.query.live; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.ORecordOperation; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @author Luigi Dell'Aquila + */ +public class OLiveQueryQueueThread extends Thread { + + private final BlockingQueue queue; + private final ConcurrentMap subscribers; + private boolean stopped = false; + + private OLiveQueryQueueThread(BlockingQueue queue, ConcurrentMap subscribers) { + this.queue = queue; + this.subscribers = subscribers; + } + + public OLiveQueryQueueThread() { + this(new LinkedBlockingQueue(), new ConcurrentHashMap()); + setName("LiveQueryQueueThread"); + this.setDaemon(true); + } + + public OLiveQueryQueueThread clone() { + return new OLiveQueryQueueThread(this.queue, this.subscribers); + } + + @Override + public void run() { + while (!stopped) { + ORecordOperation next = null; + try { + next = queue.take(); + } catch (InterruptedException e) { + break; + } + if (next == null) { + continue; + } + for (OLiveQueryListener listener : subscribers.values()) { + // TODO filter data + try { + listener.onLiveResult(next); + } catch (Exception e) { + OLogManager.instance().warn(this, "Error executing live query subscriber.", e); + } + + } + } + } + + public void stopExecution() { + this.stopped = true; + this.interrupt(); + } + + public void enqueue(ORecordOperation item) { + queue.offer(item); + } + + public Integer subscribe(Integer id, OLiveQueryListener iListener) { + subscribers.put(id, iListener); + return id; + } + + public void unsubscribe(Integer id) { + OLiveQueryListener res = subscribers.remove(id); + if (res != null) { + res.onLiveResultEnd(); + } + } + + public boolean hasListeners() { + return !subscribers.isEmpty(); + } + + public boolean hasToken(Integer key) { + return subscribers.containsKey(key); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/OIdentityChangeListener.java b/core/src/main/java/com/orientechnologies/orient/core/record/OIdentityChangeListener.java new file mode 100644 index 00000000000..21b0ccb3de4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/OIdentityChangeListener.java @@ -0,0 +1,23 @@ +package com.orientechnologies.orient.core.record; + +/** + * Listener, which is called when record identity is changed. Identity is changed if new record is saved or if transaction is + * committed and new record created inside of transaction. + */ +public interface OIdentityChangeListener { + + /** + * Called before the change of the identity is made. + * + * @param record + */ + void onBeforeIdentityChange(ORecord record); + + /** + * called afer the change of the identity is made. + * + * @param record + */ + void onAfterIdentityChange(ORecord record); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecord.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecord.java new file mode 100755 index 00000000000..f2fa16ec83d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecord.java @@ -0,0 +1,201 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.tx.OTransactionOptimistic; + +import java.io.Serializable; + +/** + * Generic record representation. The object can be reused across multiple calls to the database by using the {@link #reset()} + * method. + */ +public interface ORecord extends ORecordElement, OIdentifiable, Serializable, OSerializableStream { + /** + * Removes all the dependencies with other records. All the relationships remain in form of RecordID. If some links contain dirty + * records, the detach cannot be complete and this method returns false. + * + * @return True if the document has been successfully detached, otherwise false. + */ + boolean detach(); + + /** + * Resets the record to be reused. The record is fresh like just created. Use this method to recycle records avoiding the creation + * of them stressing the JVM Garbage Collector. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET reset(); + + /** + * Unloads current record. All information are lost but the record identity. At the next access the record will be auto-reloaded. + * Useful to free memory or to avoid to keep an old version of it. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET unload(); + + /** + * All the fields are deleted but the record identity is maintained. Use this to remove all the document's fields. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET clear(); + + /** + * Creates a copy of the record. All the record contents are copied. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET copy(); + + /** + * Returns the record identity as <cluster-id>:<cluster-position> + */ + ORID getIdentity(); + + /** + * Returns the current version number of the record. When the record is created has version = 0. At every change the storage + * increment the version number. Version number is used by Optimistic transactions to check if the record is changed in the + * meanwhile of the transaction. + * + * @see OTransactionOptimistic + * @return The version number. 0 if it's a brand new record. + */ + int getVersion(); + + /** + * Returns the database where the record belongs. + * + * @return + */ + ODatabaseDocument getDatabase(); + + /** + * Checks if the record is dirty, namely if it was changed in memory. + * + * @return True if dirty, otherwise false + */ + boolean isDirty(); + + /** + * Loads the record content in memory. If the record is in cache will be returned a new instance, so pay attention to use the + * returned. If the record is dirty, then it returns to the original content. If the record does not exist a + * ORecordNotFoundException exception is thrown. + * + * @return The record loaded or itself if the record has been reloaded from the storage. Useful to call methods in chain. + */ + RET load() throws ORecordNotFoundException; + + /** + * Loads the record content in memory. No cache is used. If the record is dirty, then it returns to the original content. If the + * record does not exist a ORecordNotFoundException exception is thrown. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET reload() throws ORecordNotFoundException; + + RET reload(final String fetchPlan, final boolean ignoreCache, boolean force) + throws ORecordNotFoundException; + + /** + * Saves in-memory changes to the database. Behavior depends by the current running transaction if any. If no transaction is + * running then changes apply immediately. If an Optimistic transaction is running then the record will be changed at commit time. + * The current transaction will continue to see the record as modified, while others not. If a Pessimistic transaction is running, + * then an exclusive lock is acquired against the record. Current transaction will continue to see the record as modified, while + * others cannot access to it since it's locked. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET save(); + + /** + * Saves in-memory changes to the database defining a specific cluster where to save it. Behavior depends by the current running + * transaction if any. If no transaction is running then changes apply immediately. If an Optimistic transaction is running then + * the record will be changed at commit time. The current transaction will continue to see the record as modified, while others + * not. If a Pessimistic transaction is running, then an exclusive lock is acquired against the record. Current transaction will + * continue to see the record as modified, while others cannot access to it since it's locked. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET save(String iCluster); + + RET save(boolean forceCreate); + + RET save(String iCluster, boolean forceCreate); + + /** + * Deletes the record from the database. Behavior depends by the current running transaction if any. If no transaction is running + * then the record is deleted immediately. If an Optimistic transaction is running then the record will be deleted at commit time. + * The current transaction will continue to see the record as deleted, while others not. If a Pessimistic transaction is running, + * then an exclusive lock is acquired against the record. Current transaction will continue to see the record as deleted, while + * others cannot access to it since it's locked. + * + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET delete(); + + /** + * Fills the record parsing the content in JSON format. + * + * @param iJson + * Object content in JSON format + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + RET fromJSON(String iJson); + + /** + * Exports the record in JSON format. + * + * @return Object content in JSON format + */ + String toJSON(); + + /** + * Exports the record in JSON format specifying additional formatting settings. + * + * @param iFormat + * Format settings separated by comma. Available settings are: + *
            + *
          • rid: exports the record's id as property "@rid"
          • + *
          • version: exports the record's version as property "@version"
          • + *
          • class: exports the record's class as property "@class"
          • + *
          • attribSameRow: exports all the record attributes in the same row
          • + *
          • indent:<level>: Indents the output if the <level> specified. Default is 0
          • + *
          + * Example: "rid,version,class,indent:6" exports record id, version and class properties along with record properties + * using an indenting level equals of 6. + * @return Object content in JSON format + */ + String toJSON(String iFormat); + + /** + * Returns the size in bytes of the record. The size can be computed only for not new records. + * + * @return the size in bytes + */ + int getSize(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordAbstract.java new file mode 100755 index 00000000000..5dfaa84d671 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordAbstract.java @@ -0,0 +1,536 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODirtyManager; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerJSON; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.OOfflineClusterException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +@SuppressWarnings({ "unchecked", "serial" }) +public abstract class ORecordAbstract implements ORecord { + protected ORecordId _recordId; + protected int _recordVersion = 0; + + protected byte[] _source; + protected int _size; + + protected transient ORecordSerializer _recordFormat; + protected boolean _dirty = true; + protected boolean _contentChanged = true; + protected ORecordElement.STATUS _status = ORecordElement.STATUS.LOADED; + protected transient Set _listeners = null; + + private transient Set newIdentityChangeListeners = null; + protected ODirtyManager _dirtyManager; + + public ORecordAbstract() { + } + + public ORecordAbstract(final byte[] iSource) { + _source = iSource; + _size = iSource.length; + unsetDirty(); + } + + public ORID getIdentity() { + return _recordId; + } + + protected ORecordAbstract setIdentity(final ORecordId iIdentity) { + _recordId = iIdentity; + getDirtyManager().setDirty(this); + return this; + } + + @Override + public ORecordElement getOwner() { + return null; + } + + public ORecord getRecord() { + return this; + } + + public boolean detach() { + return true; + } + + public ORecordAbstract clear() { + setDirty(); + invokeListenerEvent(ORecordListener.EVENT.CLEAR); + return this; + } + + public ORecordAbstract reset() { + _status = ORecordElement.STATUS.LOADED; + _recordVersion = 0; + _size = 0; + + _source = null; + setDirty(); + if (_recordId != null) + _recordId.reset(); + + invokeListenerEvent(ORecordListener.EVENT.RESET); + + return this; + } + + public byte[] toStream() { + if (_source == null) + _source = _recordFormat.toStream(this, false); + + invokeListenerEvent(ORecordListener.EVENT.MARSHALL); + + return _source; + } + + public ORecordAbstract fromStream(final byte[] iRecordBuffer) { + _dirty = false; + _contentChanged = false; + _dirtyManager = null; + _source = iRecordBuffer; + _size = iRecordBuffer != null ? iRecordBuffer.length : 0; + _status = ORecordElement.STATUS.LOADED; + + invokeListenerEvent(ORecordListener.EVENT.UNMARSHALL); + + return this; + } + + public ORecordAbstract setDirty() { + if (!_dirty && _status != STATUS.UNMARSHALLING) { + _dirty = true; + _source = null; + } + + _contentChanged = true; + return this; + } + + @Override + public void setDirtyNoChanged() { + if (!_dirty && _status != STATUS.UNMARSHALLING) { + _dirty = true; + _source = null; + } + } + + public boolean isDirty() { + return _dirty; + } + + public RET fromJSON(final String iSource, final String iOptions) { + // ORecordSerializerJSON.INSTANCE.fromString(iSource, this, null, iOptions); + ORecordSerializerJSON.INSTANCE.fromString(iSource, this, null, iOptions, false); // Add new parameter to accommodate new API, + // nothing change + return (RET) this; + } + + public RET fromJSON(final String iSource) { + ORecordSerializerJSON.INSTANCE.fromString(iSource, this, null); + return (RET) this; + } + + // Add New API to load record if rid exist + public RET fromJSON(final String iSource, boolean needReload) { + return (RET) ORecordSerializerJSON.INSTANCE.fromString(iSource, this, null, needReload); + } + + public RET fromJSON(final InputStream iContentResult) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + OIOUtils.copyStream(iContentResult, out, -1); + ORecordSerializerJSON.INSTANCE.fromString(out.toString(), this, null); + return (RET) this; + } + + public String toJSON() { + return toJSON("rid,version,class,type,attribSameRow,keepTypes,alwaysFetchEmbedded,fetchPlan:*:0"); + } + + public String toJSON(final String iFormat) { + return ORecordSerializerJSON.INSTANCE.toString(this, new StringBuilder(1024), iFormat == null ? "" : iFormat).toString(); + } + + public void toJSON(final String iFormat, final OutputStream stream) throws IOException { + stream.write(toJSON(iFormat).toString().getBytes()); + } + + public void toJSON(final OutputStream stream) throws IOException { + stream.write(toJSON().toString().getBytes()); + } + + @Override + public String toString() { + return (_recordId.isValid() ? _recordId : "") + (_source != null ? Arrays.toString(_source) : "[]") + " v" + _recordVersion; + } + + public int getVersion() { + // checkForLoading(); + return _recordVersion; + } + + protected void setVersion(final int iVersion) { + _recordVersion = iVersion; + } + + public ORecordAbstract unload() { + _status = ORecordElement.STATUS.NOT_LOADED; + _source = null; + unsetDirty(); + invokeListenerEvent(ORecordListener.EVENT.UNLOAD); + return this; + } + + public ORecord load() { + if (!getIdentity().isValid()) + throw new ORecordNotFoundException(getIdentity(), "The record has no id, probably it's new or transient yet "); + + final ORecord result = getDatabase().load(this); + + if (result == null) + throw new ORecordNotFoundException(getIdentity()); + + return result; + } + + public ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public ODatabaseDocument getDatabaseIfDefined() { + return ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + } + + public ORecord reload() { + return reload(null, true, true); + } + + public ORecord reload(final String fetchPlan) { + return reload(fetchPlan, true); + } + + public ORecord reload(final String fetchPlan, final boolean ignoreCache) { + return reload(fetchPlan, ignoreCache, true); + } + + @Override + public ORecord reload(String fetchPlan, boolean ignoreCache, boolean force) throws ORecordNotFoundException { + if (!getIdentity().isValid()) + throw new ORecordNotFoundException(getIdentity(), "The record has no id. It is probably new or still transient"); + + try { + getDatabase().reload(this, fetchPlan, ignoreCache, force); + + return this; + + } catch (OOfflineClusterException e) { + throw e; + } catch (ORecordNotFoundException e) { + throw e; + } catch (Exception e) { + throw OException.wrapException(new ORecordNotFoundException(getIdentity()), e); + } + } + + public ORecordAbstract save() { + return save(false); + } + + public ORecordAbstract save(final String iClusterName) { + return save(iClusterName, false); + } + + public ORecordAbstract save(boolean forceCreate) { + getDatabase().save(this, ODatabase.OPERATION_MODE.SYNCHRONOUS, forceCreate, null, null); + return this; + } + + public ORecordAbstract save(String iClusterName, boolean forceCreate) { + return getDatabase().save(this, iClusterName, ODatabase.OPERATION_MODE.SYNCHRONOUS, forceCreate, null, null); + } + + public ORecordAbstract delete() { + getDatabase().delete(this); + return this; + } + + public int getSize() { + return _size; + } + + @Override + public void lock(final boolean iExclusive) { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction() + .lockRecord(this, iExclusive ? OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK : OStorage.LOCKING_STRATEGY.SHARED_LOCK); + } + + @Override + public boolean isLocked() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().isLockedRecord(this); + } + + @Override + public OStorage.LOCKING_STRATEGY lockingStrategy() { + return ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().lockingStrategy(this); + } + + @Override + public void unlock() { + ODatabaseRecordThreadLocal.INSTANCE.get().getTransaction().unlockRecord(this); + } + + @Override + public int hashCode() { + return _recordId != null ? _recordId.hashCode() : 0; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + + if (obj instanceof OIdentifiable) + return _recordId.equals(((OIdentifiable) obj).getIdentity()); + + return false; + } + + public int compare(final OIdentifiable iFirst, final OIdentifiable iSecond) { + if (iFirst == null || iSecond == null) + return -1; + return iFirst.compareTo(iSecond); + } + + public int compareTo(final OIdentifiable iOther) { + if (iOther == null) + return 1; + + if (_recordId == null) + return iOther.getIdentity() == null ? 0 : 1; + + return _recordId.compareTo(iOther.getIdentity()); + } + + public ORecordElement.STATUS getInternalStatus() { + return _status; + } + + public void setInternalStatus(final ORecordElement.STATUS iStatus) { + this._status = iStatus; + } + + public ORecordAbstract copyTo(final ORecordAbstract cloned) { + cloned._source = _source; + cloned._size = _size; + cloned._recordId = _recordId.copy(); + cloned._recordVersion = _recordVersion; + cloned._status = _status; + cloned._recordFormat = _recordFormat; + cloned._listeners = null; + cloned._dirty = false; + cloned._contentChanged = false; + cloned._dirtyManager = null; + return cloned; + } + + protected ORecordAbstract fill(final ORID iRid, final int iVersion, final byte[] iBuffer, boolean iDirty) { + _recordId.setClusterId(iRid.getClusterId()); + _recordId.setClusterPosition(iRid.getClusterPosition()); + _recordVersion = iVersion; + _status = ORecordElement.STATUS.LOADED; + _source = iBuffer; + _size = iBuffer != null ? iBuffer.length : 0; + if (_source != null && _source.length > 0) { + _dirty = iDirty; + _contentChanged = iDirty; + if (!iDirty && _dirtyManager != null) + _dirtyManager.removePointed(this); + } + + return this; + } + + protected ORecordAbstract setIdentity(final int iClusterId, final long iClusterPosition) { + if (_recordId == null || _recordId == ORecordId.EMPTY_RECORD_ID) + _recordId = new ORecordId(iClusterId, iClusterPosition); + else { + _recordId.setClusterId(iClusterId); + _recordId.setClusterPosition(iClusterPosition); + } + return this; + } + + protected void unsetDirty() { + _contentChanged = false; + _dirty = false; + if (_dirtyManager != null) + _dirtyManager.removePointed(this); + } + + protected abstract byte getRecordType(); + + protected void onBeforeIdentityChanged(final ORecord iRecord) { + if (newIdentityChangeListeners != null) { + for (OIdentityChangeListener changeListener : newIdentityChangeListeners) + changeListener.onBeforeIdentityChange(this); + } + } + + protected void onAfterIdentityChanged(final ORecord iRecord) { + invokeListenerEvent(ORecordListener.EVENT.IDENTITY_CHANGED); + + if (newIdentityChangeListeners != null) { + for (OIdentityChangeListener changeListener : newIdentityChangeListeners) + changeListener.onAfterIdentityChange(this); + } + + } + + protected ODatabaseDocumentInternal getDatabaseInternal() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + protected ODatabaseDocumentInternal getDatabaseIfDefinedInternal() { + return ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + } + + /** + * Add a listener to the current document to catch all the supported events. + * + * @param iListener ODocumentListener implementation + * + * @see ORecordListener ju + */ + protected void addListener(final ORecordListener iListener) { + if (_listeners == null) + _listeners = Collections.newSetFromMap(new WeakHashMap()); + + _listeners.add(iListener); + } + + /** + * Remove the current event listener. + * + * @see ORecordListener + */ + protected void removeListener(final ORecordListener listener) { + if (_listeners != null) { + _listeners.remove(listener); + if (_listeners.isEmpty()) + _listeners = null; + } + } + + protected RET flatCopy() { + return (RET) copy(); + } + + protected void addIdentityChangeListener(OIdentityChangeListener identityChangeListener) { + if (newIdentityChangeListeners == null) + newIdentityChangeListeners = Collections.newSetFromMap(new WeakHashMap()); + newIdentityChangeListeners.add(identityChangeListener); + } + + protected void removeIdentityChangeListener(OIdentityChangeListener identityChangeListener) { + if (newIdentityChangeListeners != null) + newIdentityChangeListeners.remove(identityChangeListener); + } + + protected void setup() { + if (_recordId == null) + _recordId = new ORecordId(); + } + + protected void invokeListenerEvent(final ORecordListener.EVENT iEvent) { + if (_listeners != null) + for (final ORecordListener listener : _listeners) + if (listener != null) + listener.onEvent(this, iEvent); + } + + protected void checkForLoading() { + if (_status == ORecordElement.STATUS.NOT_LOADED && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) + reload(null, true); + } + + protected boolean isContentChanged() { + return _contentChanged; + } + + protected void setContentChanged(boolean contentChanged) { + _contentChanged = contentChanged; + } + + protected void clearSource() { + this._source = null; + } + + protected ODirtyManager getDirtyManager() { + if (this._dirtyManager == null) { + this._dirtyManager = new ODirtyManager(); + if (this.getIdentity().isNew() && getOwner() == null) + this._dirtyManager.setDirty(this); + } + return this._dirtyManager; + } + + protected void setDirtyManager(ODirtyManager dirtyManager) { + if (this._dirtyManager != null && dirtyManager != null) { + dirtyManager.merge(this._dirtyManager); + } + this._dirtyManager = dirtyManager; + if (this.getIdentity().isNew() && getOwner() == null && this._dirtyManager != null) + this._dirtyManager.setDirty(this); + } + + protected void track(OIdentifiable id) { + this.getDirtyManager().track(this, id); + } + + protected void unTrack(OIdentifiable id) { + this.getDirtyManager().unTrack(this, id); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordFactoryManager.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordFactoryManager.java new file mode 100755 index 00000000000..211f13866ef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordFactoryManager.java @@ -0,0 +1,116 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.record.impl.OBlob; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ORecordBytes; +import com.orientechnologies.orient.core.record.impl.ORecordFlat; + +/** + * Record factory. To use your own record implementation use the declareRecordType() method. Example of registration of the record + * MyRecord: + *

          + * + * declareRecordType('m', "myrecord", MyRecord.class); + * + *

          + * + * @author Sylvain Spinelli + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class ORecordFactoryManager { + protected final String[] recordTypeNames = new String[Byte.MAX_VALUE]; + protected final Class[] recordTypes = new Class[Byte.MAX_VALUE]; + protected final ORecordFactory[] recordFactories = new ORecordFactory[Byte.MAX_VALUE]; + + public interface ORecordFactory { + ORecord newRecord(); + } + + public ORecordFactoryManager() { + declareRecordType(ODocument.RECORD_TYPE, "document", ODocument.class, new ORecordFactory() { + public ORecord newRecord() { + return new ODocument(); + } + }); + declareRecordType(OBlob.RECORD_TYPE, "bytes", OBlob.class, new ORecordFactory() { + public ORecord newRecord() { + return new ORecordBytes(); + } + }); + declareRecordType(ORecordFlat.RECORD_TYPE, "flat", ORecordFlat.class, new ORecordFactory() { + public ORecord newRecord() { + return new ORecordFlat(); + } + }); + } + + public String getRecordTypeName(final byte iRecordType) { + String name = recordTypeNames[iRecordType]; + if (name == null) + throw new IllegalArgumentException("Unsupported record type: " + iRecordType); + return name; + } + + public Class getRecordTypeClass(final byte iRecordType) { + Class cls = recordTypes[iRecordType]; + if (cls == null) + throw new IllegalArgumentException("Unsupported record type: " + iRecordType); + return cls; + } + + public ORecord newInstance() { + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.get(); + try { + return (ORecord) getFactory(database.getRecordType()).newRecord(); + } catch (Exception e) { + throw new IllegalArgumentException("Unsupported record type: " + database.getRecordType(), e); + } + } + + public ORecord newInstance(final byte iRecordType) { + try { + return (ORecord) getFactory(iRecordType).newRecord(); + } catch (Exception e) { + throw new IllegalArgumentException("Unsupported record type: " + iRecordType, e); + } + } + + public void declareRecordType(byte iByte, String iName, Class iClass, final ORecordFactory iFactory) { + if (recordTypes[iByte] != null) + throw new OSystemException("Record type byte '" + iByte + "' already in use : " + recordTypes[iByte].getName()); + recordTypeNames[iByte] = iName; + recordTypes[iByte] = iClass; + recordFactories[iByte] = iFactory; + } + + protected ORecordFactory getFactory(final byte iRecordType) { + final ORecordFactory factory = recordFactories[iRecordType]; + if (factory == null) + throw new IllegalArgumentException("Record type '" + iRecordType + "' is not supported"); + return factory; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordInternal.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordInternal.java new file mode 100644 index 00000000000..891a62734ab --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordInternal.java @@ -0,0 +1,133 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODirtyManager; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; + +public class ORecordInternal { + + /** + * Internal only. Fills in one shot the record. + */ + public static ORecordAbstract fill(final ORecord record, final ORID iRid, final int iVersion, final byte[] iBuffer, + final boolean iDirty) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.fill(iRid, iVersion, iBuffer, iDirty); + return rec; + } + + /** + * Internal only. Changes the identity of the record. + */ + public static ORecordAbstract setIdentity(final ORecord record, final int iClusterId, final long iClusterPosition) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.setIdentity(iClusterId, iClusterPosition); + return rec; + } + + /** + * Internal only. Changes the identity of the record. + */ + public static ORecordAbstract setIdentity(final ORecord record, final ORecordId iIdentity) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.setIdentity(iIdentity); + return rec; + } + + /** + * Internal only. Unsets the dirty status of the record. + */ + public static void unsetDirty(final ORecord record) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.unsetDirty(); + } + + /** + * Internal only. Sets the version. + */ + public static void setVersion(final ORecord record, final int iVersion) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.setVersion(iVersion); + } + + /** + * Internal only. Return the record type. + */ + public static byte getRecordType(final ORecord record) { + final ORecordAbstract rec = (ORecordAbstract) record; + return rec.getRecordType(); + } + + public static boolean isContentChanged(final ORecord record) { + final ORecordAbstract rec = (ORecordAbstract) record; + return rec.isContentChanged(); + } + + public static void setContentChanged(final ORecord record, final boolean changed) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.setContentChanged(changed); + } + + public static void clearSource(final ORecord record) { + final ORecordAbstract rec = (ORecordAbstract) record; + rec.clearSource(); + } + + public static void addIdentityChangeListener(final ORecord record, final OIdentityChangeListener identityChangeListener) { + ((ORecordAbstract) record).addIdentityChangeListener(identityChangeListener); + } + + public static void removeIdentityChangeListener(final ORecord record, final OIdentityChangeListener identityChangeListener) { + ((ORecordAbstract) record).removeIdentityChangeListener(identityChangeListener); + } + + public static void onBeforeIdentityChanged(final ORecord record) { + ((ORecordAbstract) record).onBeforeIdentityChanged(record); + } + + public static void onAfterIdentityChanged(final ORecord record) { + ((ORecordAbstract) record).onAfterIdentityChanged(record); + } + + public static void setRecordSerializer(final ORecord record, final ORecordSerializer serializer) { + ((ORecordAbstract) record)._recordFormat = serializer; + } + + public static ODirtyManager getDirtyManager(final ORecord record) { + return ((ORecordAbstract) record).getDirtyManager(); + } + + public static void setDirtyManager(final ORecord record, final ODirtyManager dirtyManager) { + ((ORecordAbstract) record).setDirtyManager(dirtyManager); + } + + public static void track(final ORecord pointer, final OIdentifiable pointed) { + ((ORecordAbstract) pointer).track(pointed); + } + + public static void unTrack(final ORecord pointer, final OIdentifiable pointed) { + ((ORecordAbstract) pointer).unTrack(pointed); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordListener.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordListener.java new file mode 100644 index 00000000000..9492784335e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordListener.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +/** + * Listener interface to catch all the record events. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * @since 2.2 + */ +@Deprecated +public interface ORecordListener { + enum EVENT { + CLEAR, RESET, MARSHALL, UNMARSHALL, UNLOAD, IDENTITY_CHANGED + } + + void onEvent(ORecord iDocument, EVENT iEvent); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordSchemaAware.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordSchemaAware.java new file mode 100644 index 00000000000..c7a73b7de48 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordSchemaAware.java @@ -0,0 +1,158 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.orient.core.exception.OValidationException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Generic record representation with a schema definition. The record has multiple fields. Fields are also called properties. + */ +public interface ORecordSchemaAware { + + /** + * Returns the value of a field. + * + * @param iFieldName + * Field name + * @return Field value if exists, otherwise null + */ + public RET field(String iFieldName); + + /** + * Returns the value of a field forcing the return type. This is useful when you want avoid automatic conversions (for example + * record id to record) or need expressly a conversion between types. + * + * @param iFieldName + * Field name + * @param iType + * Type between the values defined in the {@link OType} enum + * @return Field value if exists, otherwise null + */ + public RET field(String iFieldName, OType iType); + + /** + * Sets the value for a field. + * + * @param iFieldName + * Field name + * @param iFieldValue + * Field value to set + * @return The Record instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + public ORecordSchemaAware field(String iFieldName, Object iFieldValue); + + /** + * Sets the value for a field forcing the type.This is useful when you want avoid automatic conversions (for example record id to + * record) or need expressly a conversion between types. + * + * + * + * @param iFieldName + * Field name + * @param iFieldValue + * Field value to set + * @param iType + * Type between the values defined in the {@link com.orientechnologies.orient.core.metadata.schema.OType} enum + * @return + */ + public ORecordSchemaAware field(String iFieldName, Object iFieldValue, OType... iType); + + /** + * Removes a field. This operation does not set the field value to null but remove the field itself. + * + * @param iFieldName + * Field name + * @return The old value contained in the remove field + */ + public Object removeField(String iFieldName); + + /** + * Tells if a field is contained in current record. + * + * @param iFieldName + * Field name + * @return true if exists, otherwise false + */ + public boolean containsField(String iFieldName); + + /** + * Returns the number of fields present in memory. + * + * @return Fields number + */ + public int fields(); + + /** + * Returns the record's field names. The returned Set object is disconnected by internal representation, so changes don't apply to + * the record. If the fields are ordered the order is maintained also in the returning collection. + * + * @return Set of string containing the field names + */ + public String[] fieldNames(); + + /** + * Returns the record's field values. The returned object array is disconnected by internal representation, so changes don't apply + * to the record. If the fields are ordered the order is maintained also in the returning collection. + * + * @return Object array of the field values + */ + public Object[] fieldValues(); + + /** + * Returns the class name associated to the current record. Can be null. Call this method after a {@link #reset()} to re-associate + * the class. + * + * @return Class name if any + */ + public String getClassName(); + + /** + * Sets the class for the current record. If the class not exists, it will be created in transparent way as empty (no fields). + * + * @param iClassName + * Class name to set + */ + public void setClassName(String iClassName); + + /** + * Sets the class for the current record only if already exists in the schema. + * + * @param iClassName + * Class name to set + */ + public void setClassNameIfExists(String iClassName); + + /** + * Returns the schema class object for the record. + * + * @return {@link OClass} instance or null if the record has no class associated + */ + public OClass getSchemaClass(); + + /** + * Validates the record against the schema constraints if defined. If the record breaks the validation rules, then a + * {@link OValidationException} exception is thrown. + * + * @throws OValidationException + */ + public void validate() throws OValidationException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordStringable.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordStringable.java new file mode 100644 index 00000000000..dd4202bdf6a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordStringable.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record; + +/** + * Generic record representation without a schema definition. The object can be reused across call to the database. + */ +public interface ORecordStringable { + + public String value(); + + public ORecordStringable value(String iValue); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/ORecordVersionHelper.java b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordVersionHelper.java new file mode 100755 index 00000000000..c0a45323eba --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/ORecordVersionHelper.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.record; + +import com.orientechnologies.common.serialization.OBinaryConverter; +import com.orientechnologies.common.serialization.OBinaryConverterFactory; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +/** + * Static helper class to manage record version. + * + * @author Artem Orobets (enisher-at-gmail.com) + * @author Luca Garulli + */ +public class ORecordVersionHelper { + public static final OBinaryConverter CONVERTER = OBinaryConverterFactory.getConverter(); + public static final int SERIALIZED_SIZE = OBinaryProtocol.SIZE_INT; + + protected ORecordVersionHelper() { + } + + public static int increment(final int version) { + if (isTombstone(version)) + throw new IllegalStateException("Record was deleted and cannot be updated."); + + return version + 1; + } + + public static int decrement(final int version) { + if (isTombstone(version)) + throw new IllegalStateException("Record was deleted and cannot be updated."); + + return version - 1; + } + + public static boolean isUntracked(final int version) { + return version == -1; + } + + public static int setRollbackMode(final int version) { + return Integer.MIN_VALUE + version; + } + + public static int clearRollbackMode(final int version) { + return version - Integer.MIN_VALUE; + } + + public static boolean isTemporary(final int version) { + return version < -1; + } + + public static boolean isValid(final int version) { + return version > -1; + } + + public static boolean isTombstone(final int version) { + return version < 0; + } + + public static byte[] toStream(final int version) { + return OBinaryProtocol.int2bytes(version); + } + + public static int fromStream(final byte[] stream) { + return OBinaryProtocol.bytes2int(stream); + } + + public static int reset() { + return 0; + } + + public static int disable() { + return -1; + } + + public static int compareTo(final int v1, final int v2) { + final int myVersion; + if (isTombstone(v1)) + myVersion = -v1; + else + myVersion = v1; + + final int otherVersion; + if (isTombstone(v2)) + otherVersion = -v2; + else + otherVersion = v2; + + if (myVersion == otherVersion) + return 0; + + if (myVersion < otherVersion) + return -1; + + return 1; + } + + public static String toString(final int version) { + return String.valueOf(version); + } + + public static int fromString(final String string) { + return Integer.parseInt(string); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/OBlob.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/OBlob.java new file mode 100644 index 00000000000..336101a5d17 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/OBlob.java @@ -0,0 +1,20 @@ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.record.ORecord; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Created by tglman on 05/01/16. + */ +public interface OBlob extends ORecord { + byte RECORD_TYPE = 'b'; + + int fromInputStream(final InputStream in) throws IOException; + + int fromInputStream(final InputStream in, final int maxSize) throws IOException; + + void toOutputStream(final OutputStream out) throws IOException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODirtyManager.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODirtyManager.java new file mode 100644 index 00000000000..c7c9489d262 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODirtyManager.java @@ -0,0 +1,220 @@ +/* + * + * * Copyright 2014 OrientDB LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; + +import java.util.*; +import java.util.Map.Entry; + +/** + * @author Emanuele Tagliafetti + */ +public class ODirtyManager { + + private ODirtyManager overrider; + private Map> references; + private Set newRecords; + private Set updateRecords; + + public void setDirty(ORecord record) { + ODirtyManager real = getReal(); + if (record.getIdentity().isNew() && !record.getIdentity().isTemporary()) { + if (real.newRecords == null) + real.newRecords = Collections.newSetFromMap(new IdentityHashMap()); + real.newRecords.add(record); + } else { + if (real.updateRecords == null) + real.updateRecords = Collections.newSetFromMap(new IdentityHashMap()); + real.updateRecords.add(record); + } + } + + public ODirtyManager getReal() { + ODirtyManager real = this; + while (real.overrider != null) { + real = real.overrider; + } + if (this.overrider != null && this.overrider != real) + this.overrider = real; + return real; + } + + public Set getNewRecords() { + return getReal().newRecords; + } + + public Set getUpdateRecords() { + return getReal().updateRecords; + } + + public Map> getReferences() { + return getReal().references; + } + + public boolean isSame(ODirtyManager other) { + // other = other.getReal(); + // if (overrider != null) + // return overrider.isSame(other); + return this.getReal() == other.getReal(); + } + + public void merge(ODirtyManager toMerge) { + if (isSame(toMerge)) + return; + final Set newRecords = toMerge.getNewRecords(); + if (newRecords != null) { + if (this.newRecords == null) + this.newRecords = Collections.newSetFromMap(new IdentityHashMap(newRecords.size())); + this.newRecords.addAll(newRecords); + } + final Set updateRecords = toMerge.getUpdateRecords(); + if (updateRecords != null) { + if (this.updateRecords == null) + this.updateRecords = Collections.newSetFromMap(new IdentityHashMap(updateRecords.size())); + this.updateRecords.addAll(updateRecords); + } + if (toMerge.getReferences() != null) { + if (references == null) + references = new IdentityHashMap>(); + for (Entry> entry : toMerge.getReferences().entrySet()) { + List refs = references.get(entry.getKey()); + if (refs == null) + references.put(entry.getKey(), entry.getValue()); + else + refs.addAll(entry.getValue()); + } + } + toMerge.override(this); + } + + public void track(ORecord pointing, OIdentifiable pointed) { + getReal().internalTrack(pointing, pointed); + } + + public void unTrack(ORecord pointing, OIdentifiable pointed) { + getReal().internalUnTrack(pointing, pointed); + } + + private void internalUnTrack(ORecord pointing, OIdentifiable pointed) { + if (references == null) + return; + + if (pointed.getIdentity().isNew()) { + List refs = references.get(pointing); + if (refs == null) + return; + if (!(pointed instanceof ODocument) || !((ODocument) pointed).isEmbedded()) { + refs.remove(pointed); + } + } + } + + private void internalTrack(ORecord pointing, OIdentifiable pointed) { + if (pointing instanceof ODocument) { + if (((ODocument) pointing).isEmbedded()) { + + ORecordElement ele = pointing.getOwner(); + while (!(ele instanceof ODocument) && ele != null && ele.getOwner() != null) + ele = ele.getOwner(); + if (ele != null) + pointing = (ORecord) ele; + } + } + if (pointed.getIdentity().isNew()) { + if (!(pointed instanceof ODocument) || !((ODocument) pointed).isEmbedded()) { + if (references == null) { + references = new IdentityHashMap>(); + } + List refs = references.get(pointing); + if (refs == null) { + refs = new ArrayList(); + references.put((ODocument) pointing, refs); + } + refs.add(pointed); + } else if (pointed instanceof ODocument) { + List point = ORecordInternal.getDirtyManager((ORecord) pointed).getPointed((ORecord) pointed); + if (point != null && point.size() > 0) { + if (references == null) { + references = new IdentityHashMap>(); + } + List refs = references.get(pointing); + if (refs == null) { + refs = new ArrayList(); + references.put((ODocument) pointing, refs); + } + for (OIdentifiable embPoint : point) { + refs.add(embPoint); + } + } + } + } + if (pointed instanceof ORecord) { + ORecordInternal.setDirtyManager((ORecord) pointed, this); + } + } + + private void override(ODirtyManager oDirtyManager) { + ODirtyManager real = getReal(); + oDirtyManager = oDirtyManager.getReal(); + if (real == oDirtyManager) + return; + real.overrider = oDirtyManager; + real.newRecords = null; + real.updateRecords = null; + real.references = null; + } + + public void clearForSave() { + ODirtyManager real = getReal(); + real.newRecords = null; + real.updateRecords = null; + } + + public List getPointed(ORecord rec) { + ODirtyManager real = getReal(); + if (real.references == null) + return null; + return real.references.get(rec); + } + + public void removeNew(ORecord record) { + ODirtyManager real = getReal(); + if (real.newRecords != null) + real.newRecords.remove(record); + } + + public void removePointed(ORecord record) { + ODirtyManager real = getReal(); + if (real.references != null) { + real.references.remove(record); + if (real.references.size() == 0) + references = null; + } + } + + public void clear() { + clearForSave(); + getReal().references = null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocument.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocument.java new file mode 100755 index 00000000000..7dac397ed1f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocument.java @@ -0,0 +1,2828 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.*; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.iterator.OEmptyMapEntryIterator; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.*; +import com.orientechnologies.orient.core.metadata.security.OIdentity; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; +import com.orientechnologies.orient.core.record.*; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerNetwork; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.tx.OTransaction; +import com.orientechnologies.orient.core.tx.OTransactionOptimistic; + +import java.io.*; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.Map.Entry; + +/** + * Document representation to handle values dynamically. Can be used in schema-less, schema-mixed and schema-full modes. Fields can + * be added at run-time. Instances can be reused across calls by using the reset() before to re-use. + */ +@SuppressWarnings({ "unchecked" }) +public class ODocument extends ORecordAbstract + implements Iterable>, ORecordSchemaAware, ODetachable, Externalizable { + + public static final byte RECORD_TYPE = 'd'; + protected static final String[] EMPTY_STRINGS = new String[] {}; + private static final long serialVersionUID = 1L; + protected int _fieldSize; + + protected Map _fields; + + protected boolean _trackingChanges = true; + protected boolean _ordered = true; + protected boolean _lazyLoad = true; + protected boolean _allowChainedAccess = true; + protected transient List> _owners = null; + protected OImmutableSchema _schema; + private String _className; + private OImmutableClass _immutableClazz; + private int _immutableSchemaVersion = 1; + + /** + * Internal constructor used on unmarshalling. + */ + public ODocument() { + setup(); + } + + /** + * Creates a new instance by the raw stream usually read from the database. New instances are not persistent until {@link #save()} + * is called. + * + * @param iSource Raw stream + */ + public ODocument(final byte[] iSource) { + _source = iSource; + setup(); + } + + /** + * Creates a new instance by the raw stream usually read from the database. New instances are not persistent until {@link #save()} + * is called. + * + * @param iSource Raw stream as InputStream + */ + public ODocument(final InputStream iSource) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + OIOUtils.copyStream(iSource, out, -1); + _source = out.toByteArray(); + setup(); + } + + /** + * Creates a new instance in memory linked by the Record Id to the persistent one. New instances are not persistent until + * {@link #save()} is called. + * + * @param iRID Record Id + */ + public ODocument(final ORID iRID) { + setup(); + _recordId = (ORecordId) iRID; + _status = STATUS.NOT_LOADED; + _dirty = false; + _contentChanged = false; + } + + /** + * Creates a new instance in memory of the specified class, linked by the Record Id to the persistent one. New instances are not + * persistent until {@link #save()} is called. + * + * @param iClassName Class name + * @param iRID Record Id + */ + public ODocument(final String iClassName, final ORID iRID) { + this(iClassName); + _recordId = (ORecordId) iRID; + + final ODatabaseDocumentInternal database = getDatabaseInternal(); + if (_recordId.getClusterId() > -1 && database.getStorageVersions().classesAreDetectedByClusterId()) { + final OSchema schema = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot(); + final OClass cls = schema.getClassByClusterId(_recordId.getClusterId()); + if (cls != null && !cls.getName().equals(iClassName)) + throw new IllegalArgumentException( + "Cluster id does not correspond class name should be " + iClassName + " but found " + cls.getName()); + } + + _dirty = false; + _contentChanged = false; + _status = STATUS.NOT_LOADED; + } + + /** + * Creates a new instance in memory of the specified class. New instances are not persistent until {@link #save()} is called. + * + * @param iClassName Class name + */ + public ODocument(final String iClassName) { + setup(); + setClassName(iClassName); + } + + /** + * Creates a new instance in memory of the specified schema class. New instances are not persistent until {@link #save()} is + * called. The database reference is taken from the thread local. + * + * @param iClass OClass instance + */ + public ODocument(final OClass iClass) { + this(iClass != null ? iClass.getName() : null); + } + + /** + * Fills a document passing the field array in form of pairs of field name and value. + * + * @param iFields Array of field pairs + */ + public ODocument(final Object[] iFields) { + setup(); + if (iFields != null && iFields.length > 0) + for (int i = 0; i < iFields.length; i += 2) { + field(iFields[i].toString(), iFields[i + 1]); + } + } + + /** + * Fills a document passing a map of key/values where the key is the field name and the value the field's value. + * + * @param iFieldMap Map of Object/Object + */ + public ODocument(final Map iFieldMap) { + setup(); + if (iFieldMap != null && !iFieldMap.isEmpty()) + for (Entry entry : iFieldMap.entrySet()) { + field(entry.getKey().toString(), entry.getValue()); + } + } + + /** + * Fills a document passing the field names/values pair, where the first pair is mandatory. + */ + public ODocument(final String iFieldName, final Object iFieldValue, final Object... iFields) { + this(iFields); + field(iFieldName, iFieldValue); + } + + protected static void validateField(ODocument iRecord, OImmutableProperty p) throws OValidationException { + final Object fieldValue; + ODocumentEntry entry = iRecord._fields.get(p.getName()); + if (entry != null && entry.exist()) { + // AVOID CONVERSIONS: FASTER! + fieldValue = entry.value; + + if (p.isNotNull() && fieldValue == null) + // NULLITY + throw new OValidationException("The field '" + p.getFullName() + "' cannot be null, record: " + iRecord); + + if (fieldValue != null && p.getRegexp() != null && p.getType().equals(OType.STRING)) { + // REGEXP + if (!((String) fieldValue).matches(p.getRegexp())) + throw new OValidationException( + "The field '" + p.getFullName() + "' does not match the regular expression '" + p.getRegexp() + "'. Field value is: " + + fieldValue + ", record: " + iRecord); + } + + } else { + if (p.isMandatory()) { + throw new OValidationException("The field '" + p.getFullName() + "' is mandatory, but not found on record: " + iRecord); + } + fieldValue = null; + } + + final OType type = p.getType(); + + if (fieldValue != null && type != null) { + // CHECK TYPE + switch (type) { + case LINK: + validateLink(p, fieldValue, false); + break; + case LINKLIST: + if (!(fieldValue instanceof List)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as LINKLIST but an incompatible type is used. Value: " + + fieldValue); + validateLinkCollection(p, (Collection) fieldValue, entry); + break; + case LINKSET: + if (!(fieldValue instanceof Set)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as LINKSET but an incompatible type is used. Value: " + + fieldValue); + validateLinkCollection(p, (Collection) fieldValue, entry); + break; + case LINKMAP: + if (!(fieldValue instanceof Map)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as LINKMAP but an incompatible type is used. Value: " + + fieldValue); + validateLinkCollection(p, ((Map) fieldValue).values(), entry); + break; + + case LINKBAG: + if (!(fieldValue instanceof ORidBag)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as LINKBAG but an incompatible type is used. Value: " + + fieldValue); + validateLinkCollection(p, (Iterable) fieldValue, entry); + break; + case EMBEDDED: + validateEmbedded(p, fieldValue); + break; + case EMBEDDEDLIST: + if (!(fieldValue instanceof List)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as EMBEDDEDLIST but an incompatible type is used. Value: " + + fieldValue); + if (p.getLinkedClass() != null) { + for (Object item : ((List) fieldValue)) + validateEmbedded(p, item); + } else if (p.getLinkedType() != null) { + for (Object item : ((List) fieldValue)) + validateType(p, item); + } + break; + case EMBEDDEDSET: + if (!(fieldValue instanceof Set)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as EMBEDDEDSET but an incompatible type is used. Value: " + + fieldValue); + if (p.getLinkedClass() != null) { + for (Object item : ((Set) fieldValue)) + validateEmbedded(p, item); + } else if (p.getLinkedType() != null) { + for (Object item : ((Set) fieldValue)) + validateType(p, item); + } + break; + case EMBEDDEDMAP: + if (!(fieldValue instanceof Map)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as EMBEDDEDMAP but an incompatible type is used. Value: " + + fieldValue); + if (p.getLinkedClass() != null) { + for (Entry colleEntry : ((Map) fieldValue).entrySet()) + validateEmbedded(p, colleEntry.getValue()); + } else if (p.getLinkedType() != null) { + for (Entry collEntry : ((Map) fieldValue).entrySet()) + validateType(p, collEntry.getValue()); + } + break; + } + } + + if (p.getMin() != null && fieldValue != null) { + // MIN + final String min = p.getMin(); + if (p.getMinComparable().compareTo(fieldValue) > 0) { + switch (p.getType()) { + case STRING: + throw new OValidationException( + "The field '" + p.getFullName() + "' contains fewer characters than " + min + " requested"); + case DATE: + case DATETIME: + throw new OValidationException( + "The field '" + p.getFullName() + "' contains the date " + fieldValue + " which precedes the first acceptable date (" + + min + ")"); + case BINARY: + throw new OValidationException("The field '" + p.getFullName() + "' contains fewer bytes than " + min + " requested"); + case EMBEDDEDLIST: + case EMBEDDEDSET: + case LINKLIST: + case LINKSET: + case EMBEDDEDMAP: + case LINKMAP: + throw new OValidationException("The field '" + p.getFullName() + "' contains fewer items than " + min + " requested"); + default: + throw new OValidationException("The field '" + p.getFullName() + "' is less than " + min); + } + } + } + + if (p.getMaxComparable() != null && fieldValue != null) { + final String max = p.getMax(); + if (p.getMaxComparable().compareTo(fieldValue) < 0) { + switch (p.getType()) { + case STRING: + throw new OValidationException("The field '" + p.getFullName() + "' contains more characters than " + max + " requested"); + case DATE: + case DATETIME: + throw new OValidationException( + "The field '" + p.getFullName() + "' contains the date " + fieldValue + " which is after the last acceptable date (" + + max + ")"); + case BINARY: + throw new OValidationException("The field '" + p.getFullName() + "' contains more bytes than " + max + " requested"); + case EMBEDDEDLIST: + case EMBEDDEDSET: + case LINKLIST: + case LINKSET: + case EMBEDDEDMAP: + case LINKMAP: + throw new OValidationException("The field '" + p.getFullName() + "' contains more items than " + max + " requested"); + default: + throw new OValidationException("The field '" + p.getFullName() + "' is greater than " + max); + } + } + } + + if (p.isReadonly() && !ORecordVersionHelper.isTombstone(iRecord.getVersion())) { + if (entry != null && (entry.changed || entry.timeLine != null) && !entry.created) { + // check if the field is actually changed by equal. + // this is due to a limitation in the merge algorithm used server side marking all non simple fields as dirty + Object orgVal = entry.original; + boolean simple = fieldValue != null ? OType.isSimpleType(fieldValue) : OType.isSimpleType(orgVal); + if ((simple) || (fieldValue != null && orgVal == null) || (fieldValue == null && orgVal != null) || (fieldValue != null + && !fieldValue.equals(orgVal))) + throw new OValidationException( + "The field '" + p.getFullName() + "' is immutable and cannot be altered. Field value is: " + entry.value); + } + } + } + + protected static void validateLinkCollection(final OProperty property, Iterable values, ODocumentEntry value) { + if (property.getLinkedClass() != null) { + if (value.timeLine != null) { + List> event = value.timeLine.getMultiValueChangeEvents(); + for (OMultiValueChangeEvent object : event) { + if (object.getChangeType() == OMultiValueChangeEvent.OChangeType.ADD + || object.getChangeType() == OMultiValueChangeEvent.OChangeType.UPDATE && object.getValue() != null) + validateLink(property, object.getValue(), OSecurityShared.ALLOW_FIELDS.contains(property.getName())); + } + } else { + boolean autoconvert = false; + if (values instanceof ORecordLazyMultiValue) { + autoconvert = ((ORecordLazyMultiValue) values).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) values).setAutoConvertToRecord(false); + } + for (Object object : values) { + validateLink(property, object, OSecurityShared.ALLOW_FIELDS.contains(property.getName())); + } + if (values instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) values).setAutoConvertToRecord(autoconvert); + } + } + } + + protected static void validateType(final OProperty p, final Object value) { + if (value != null) + if (OType.convert(value, p.getLinkedType().getDefaultJavaType()) == null) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " of type '" + p.getLinkedType() + + "' but the value is " + value); + } + + protected static void validateLink(final OProperty p, final Object fieldValue, boolean allowNull) { + if (fieldValue == null) { + if (allowNull) + return; + else + throw new OValidationException("The field '" + p.getFullName() + "' has been declared as " + p.getType() + + " but contains a null record (probably a deleted record?)"); + } + + final ORecord linkedRecord; + if (!(fieldValue instanceof OIdentifiable)) + throw new OValidationException("The field '" + p.getFullName() + "' has been declared as " + p.getType() + + " but the value is not a record or a record-id"); + final OClass schemaClass = p.getLinkedClass(); + if (schemaClass != null && !schemaClass.isSubClassOf(OIdentity.CLASS_NAME)) { + // DON'T VALIDATE OUSER AND OROLE FOR SECURITY RESTRICTIONS + + final ORID rid = ((OIdentifiable) fieldValue).getIdentity(); + + if (!schemaClass.hasPolymorphicClusterId(rid.getClusterId())) { + linkedRecord = ((OIdentifiable) fieldValue).getRecord(); + if (linkedRecord != null) { + if (!(linkedRecord instanceof ODocument)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " of type '" + schemaClass + + "' but the value is the record " + linkedRecord.getIdentity() + " that is not a document"); + + final ODocument doc = (ODocument) linkedRecord; + + // AT THIS POINT CHECK THE CLASS ONLY IF != NULL BECAUSE IN CASE OF GRAPHS THE RECORD COULD BE PARTIAL + if (doc.getImmutableSchemaClass() != null && !schemaClass.isSuperClassOf(doc.getImmutableSchemaClass())) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " of type '" + schemaClass.getName() + + "' but the value is the document " + linkedRecord.getIdentity() + " of class '" + doc + .getImmutableSchemaClass() + "'"); + } + } + } + } + + protected static void validateEmbedded(final OProperty p, final Object fieldValue) { + if (fieldValue == null) + return; + if (fieldValue instanceof ORecordId) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " but the value is the RecordID " + + fieldValue); + else if (fieldValue instanceof OIdentifiable) { + final OIdentifiable embedded = (OIdentifiable) fieldValue; + if (embedded.getIdentity().isValid()) + throw new OValidationException("The field '" + p.getFullName() + "' has been declared as " + p.getType() + + " but the value is a document with the valid RecordID " + fieldValue); + + final OClass embeddedClass = p.getLinkedClass(); + if (embeddedClass != null) { + final ORecord embeddedRecord = embedded.getRecord(); + if (!(embeddedRecord instanceof ODocument)) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " with linked class '" + embeddedClass + + "' but the record was not a document"); + + final ODocument doc = (ODocument) embeddedRecord; + if (doc.getImmutableSchemaClass() == null) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " with linked class '" + embeddedClass + + "' but the record has no class"); + + if (!(doc.getImmutableSchemaClass().isSubClassOf(embeddedClass))) + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " with linked class '" + embeddedClass + + "' but the record is of class '" + doc.getImmutableSchemaClass().getName() + + "' that is not a subclass of that"); + + doc.validate(); + } + + } else + throw new OValidationException( + "The field '" + p.getFullName() + "' has been declared as " + p.getType() + " but an incompatible type is used. Value: " + + fieldValue); + } + + /** + * Copies the current instance to a new one. Hasn't been choose the clone() to let ODocument return type. Once copied the new + * instance has the same identity and values but all the internal structure are totally independent by the source. + */ + public ODocument copy() { + return (ODocument) copyTo(new ODocument()); + } + + /** + * Copies all the fields into iDestination document. + */ + @Override + public ORecordAbstract copyTo(final ORecordAbstract iDestination) { + // TODO: REMOVE THIS + checkForFields(); + + ODocument destination = (ODocument) iDestination; + + super.copyTo(iDestination); + + destination._ordered = _ordered; + + destination._className = _className; + destination._immutableSchemaVersion = -1; + destination._immutableClazz = null; + + destination._trackingChanges = _trackingChanges; + if (_owners != null) + destination._owners = new ArrayList>(_owners); + else + destination._owners = null; + + if (_fields != null) { + destination._fields = _fields instanceof LinkedHashMap ? + new LinkedHashMap() : + new HashMap(); + for (Entry entry : _fields.entrySet()) { + ODocumentEntry docEntry = entry.getValue().clone(); + destination._fields.put(entry.getKey(), docEntry); + docEntry.value = ODocumentHelper.cloneValue(destination, entry.getValue().value); + } + } else + destination._fields = null; + destination._fieldSize = _fieldSize; + destination.addAllMultiValueChangeListeners(); + + destination._dirty = _dirty; // LEAVE IT AS LAST TO AVOID SOMETHING SET THE FLAG TO TRUE + destination._contentChanged = _contentChanged; + + return destination; + } + + /** + * Returns an empty record as place-holder of the current. Used when a record is requested, but only the identity is needed. + * + * @return placeholder of this document + */ + public ORecord placeholder() { + final ODocument cloned = new ODocument(); + cloned._source = null; + cloned._recordId = _recordId; + cloned._status = STATUS.NOT_LOADED; + cloned._dirty = false; + cloned._contentChanged = false; + return cloned; + } + + /** + * Detaches all the connected records. If new records are linked to the document the detaching cannot be completed and false will + * be returned. RidBag types cannot be fully detached when the database is connected using "remote" protocol. + * + * @return true if the record has been detached, otherwise false + */ + public boolean detach() { + deserializeFields(); + boolean fullyDetached = true; + + if (_fields != null) { + Object fieldValue; + for (Map.Entry entry : _fields.entrySet()) { + fieldValue = entry.getValue().value; + + if (fieldValue instanceof ORecord) + if (((ORecord) fieldValue).getIdentity().isNew()) + fullyDetached = false; + else + entry.getValue().value = ((ORecord) fieldValue).getIdentity(); + + if (fieldValue instanceof ODetachable) { + if (!((ODetachable) fieldValue).detach()) + fullyDetached = false; + } + } + } + + return fullyDetached; + } + + /** + * Loads the record using a fetch plan. Example: + *

          + * doc.load( "*:3" ); // LOAD THE DOCUMENT BY EARLY FETCHING UP TO 3rd LEVEL OF CONNECTIONS + *

          + * + * @param iFetchPlan Fetch plan to use + */ + public ODocument load(final String iFetchPlan) { + return load(iFetchPlan, false); + } + + /** + * Loads the record using a fetch plan. Example: + *

          + * doc.load( "*:3", true ); // LOAD THE DOCUMENT BY EARLY FETCHING UP TO 3rd LEVEL OF CONNECTIONS IGNORING THE CACHE + *

          + * + * @param iIgnoreCache Ignore the cache or use it + */ + public ODocument load(final String iFetchPlan, boolean iIgnoreCache) { + Object result; + try { + result = getDatabase().load(this, iFetchPlan, iIgnoreCache); + } catch (Exception e) { + throw OException.wrapException(new ORecordNotFoundException(getIdentity()), e); + } + + if (result == null) + throw new ORecordNotFoundException(getIdentity()); + + return (ODocument) result; + } + + @Deprecated + public ODocument load(final String iFetchPlan, boolean iIgnoreCache, boolean loadTombstone) { + Object result; + try { + result = getDatabase().load(this, iFetchPlan, iIgnoreCache, loadTombstone, OStorage.LOCKING_STRATEGY.DEFAULT); + } catch (Exception e) { + throw OException.wrapException(new ORecordNotFoundException(getIdentity()), e); + } + + if (result == null) + throw new ORecordNotFoundException(getIdentity()); + + return (ODocument) result; + } + + @Override + public ODocument reload(final String fetchPlan, final boolean ignoreCache) { + super.reload(fetchPlan, ignoreCache); + if (!_lazyLoad) { + checkForLoading(); + checkForFields(); + } + return this; + } + + public boolean hasSameContentOf(final ODocument iOther) { + final ODatabaseDocumentInternal currentDb = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + return ODocumentHelper.hasSameContentOf(this, currentDb, iOther, currentDb, null); + } + + @Override + public byte[] toStream() { + if (_recordFormat == null) + setup(); + return toStream(false); + } + + /** + * Returns the document as Map String,Object . If the document has identity, then the @rid entry is valued. If the document has a + * class, then the @class entry is valued. + * + * @since 2.0 + */ + public Map toMap() { + final Map map = new HashMap(); + for (String field : fieldNames()) + map.put(field, field(field)); + + final ORID id = getIdentity(); + if (id.isValid()) + map.put(ODocumentHelper.ATTRIBUTE_RID, id); + + final String className = getClassName(); + if (className != null) + map.put(ODocumentHelper.ATTRIBUTE_CLASS, className); + + return map; + } + + /** + * Dumps the instance as string. + */ + @Override + public String toString() { + return toString(new HashSet()); + } + + /** + * Fills the ODocument directly with the string representation of the document itself. Use it for faster insertion but pay + * attention to respect the OrientDB record format. + *

          + * + * record.reset();
          + * record.setClassName("Account");
          + * record.fromString(new String("Account@id:" + data.getCyclesDone() + ",name:'Luca',surname:'Garulli',birthDate:" + date.getTime()
          + * + ",salary:" + 3000f + i));
          + * record.save();
          + *
          + *

          + * + * @param iValue String representation of the record. + */ + @Deprecated + public void fromString(final String iValue) { + _dirty = true; + _contentChanged = true; + try { + _source = iValue.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error reading content from string"), e); + } + + removeAllCollectionChangeListeners(); + + _fields = null; + _fieldSize = 0; + } + + /** + * Returns the set of field names. + */ + public String[] fieldNames() { + checkForLoading(); + + if (_status == ORecordElement.STATUS.LOADED && _source != null && ODatabaseRecordThreadLocal.INSTANCE.isDefined() + && !ODatabaseRecordThreadLocal.INSTANCE.get().isClosed()) { + // DESERIALIZE FIELD NAMES ONLY (SUPPORTED ONLY BY BINARY SERIALIZER) + final String[] fieldNames = _recordFormat.getFieldNames(this, _source); + if (fieldNames != null) + return fieldNames; + } + + checkForFields(); + + if (_fields == null || _fields.size() == 0) + return EMPTY_STRINGS; + final List names = new ArrayList(_fields.size()); + for (Entry entry : _fields.entrySet()) { + if (entry.getValue().exist()) + names.add(entry.getKey()); + } + return names.toArray(new String[names.size()]); + } + + /** + * Returns the array of field values. + */ + public Object[] fieldValues() { + checkForLoading(); + checkForFields(); + Object[] res = new Object[_fields.size()]; + int i = 0; + for (ODocumentEntry entry : _fields.values()) { + res[i++] = entry.value; + } + return res; + } + + public RET rawField(final String iFieldName) { + if (iFieldName == null || iFieldName.length() == 0) + return null; + + checkForLoading(); + if (!checkForFields(iFieldName)) + // NO FIELDS + return null; + + // OPTIMIZATION + if (!_allowChainedAccess || (iFieldName.charAt(0) != '@' && OStringSerializerHelper.indexOf(iFieldName, 0, '.', '[') == -1)) { + ODocumentEntry entry = _fields.get(iFieldName); + if (entry != null && entry.exist()) + return (RET) entry.value; + else + return null; + } + + // NOT FOUND, PARSE THE FIELD NAME + return (RET) ODocumentHelper.getFieldValue(this, iFieldName); + } + + /** + * Evaluates a SQL expression against current document. Example: long amountPlusVat = doc.eval("amount * 120 / 100"); + * + * @param iExpression SQL expression to evaluate. + * @return The result of expression + * @throws OQueryParsingException in case the expression is not valid + */ + public Object eval(final String iExpression) { + return eval(iExpression, null); + } + + /** + * Evaluates a SQL expression against current document by passing a context. The expression can refer to the variables contained + * in the context. Example: + * OCommandContext context = new OBasicCommandContext().setVariable("vat", 20); + * long amountPlusVat = doc.eval("amount * (100+$vat) / 100", context); + * + * + * @param iExpression SQL expression to evaluate. + * @return The result of expression + * @throws OQueryParsingException in case the expression is not valid + */ + public Object eval(final String iExpression, final OCommandContext iContext) { + return new OSQLPredicate(iExpression).evaluate(this, null, iContext); + } + + /** + * Reads the field value. + * + * @param iFieldName field name + * @return field value if defined, otherwise null + */ + public RET field(final String iFieldName) { + RET value = this.rawField(iFieldName); + + if (!iFieldName.startsWith("@") && _lazyLoad && value instanceof ORID && (((ORID) value).isPersistent() || ((ORID) value) + .isNew()) && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) { + // CREATE THE DOCUMENT OBJECT IN LAZY WAY + RET newValue = (RET) getDatabase().load((ORID) value); + if (newValue != null) { + unTrack((ORID) value); + track((OIdentifiable) newValue); + value = newValue; + if(this.isTrackingChanges()) { + ORecordInternal.setDirtyManager((ORecord) value, this.getDirtyManager()); + } + if (!iFieldName.contains(".")) { + ODocumentEntry entry = _fields.get(iFieldName); + removeCollectionChangeListener(entry, entry.value); + entry.value = value; + addCollectionChangeListener(entry); + } + } + } + + return value; + } + + /** + * Reads the field value forcing the return type. Use this method to force return of ORID instead of the entire document by + * passing ORID.class as iFieldType. + * + * @param iFieldName field name + * @param iFieldType Forced type. + * @return field value if defined, otherwise null + */ + public RET field(final String iFieldName, final Class iFieldType) { + RET value = this.rawField(iFieldName); + + if (value != null) + value = ODocumentHelper.convertField(this, iFieldName, OType.getTypeByClass(iFieldType), iFieldType, value); + + return value; + } + + /** + * Reads the field value forcing the return type. Use this method to force return of binary data. + * + * @param iFieldName field name + * @param iFieldType Forced type. + * @return field value if defined, otherwise null + */ + public RET field(final String iFieldName, final OType iFieldType) { + RET value = (RET) field(iFieldName); + OType original; + if (iFieldType != null && iFieldType != (original = fieldType(iFieldName))) { + // this is needed for the csv serializer that don't give back values + if (original == null) { + original = OType.getTypeByValue(value); + if (iFieldType == original) + return value; + } + + final Object newValue; + + if (iFieldType == OType.BINARY && value instanceof String) + newValue = OStringSerializerHelper.getBinaryContent(value); + else if (iFieldType == OType.DATE && value instanceof Long) + newValue = new Date((Long) value); + else if ((iFieldType == OType.EMBEDDEDSET || iFieldType == OType.LINKSET) && value instanceof List) + newValue = Collections.unmodifiableSet((Set) ODocumentHelper.convertField(this, iFieldName, iFieldType, null, value)); + else if ((iFieldType == OType.EMBEDDEDLIST || iFieldType == OType.LINKLIST) && value instanceof Set) + newValue = Collections.unmodifiableList((List) ODocumentHelper.convertField(this, iFieldName, iFieldType, null, value)); + else if ((iFieldType == OType.EMBEDDEDMAP || iFieldType == OType.LINKMAP) && value instanceof Map) + newValue = Collections.unmodifiableMap((Map) ODocumentHelper.convertField(this, iFieldName, iFieldType, null, value)); + else + newValue = OType.convert(value, iFieldType.getDefaultJavaType()); + + if (newValue != null) + value = (RET) newValue; + + } + return value; + } + + /** + * Writes the field value. This method sets the current document as dirty. + * + * @param iFieldName field name. If contains dots (.) the change is applied to the nested documents in chain. To disable this feature call + * {@link #setAllowChainedAccess(boolean)} to false. + * @param iPropertyValue field value + * @return The Record instance itself giving a "fluent interface". Useful to call multiple methods in chain. + */ + public ODocument field(final String iFieldName, Object iPropertyValue) { + return field(iFieldName, iPropertyValue, OCommonConst.EMPTY_TYPES_ARRAY); + } + + /** + * Fills a document passing the field names/values. + */ + public ODocument fields(final String iFieldName, final Object iFieldValue, final Object... iFields) { + if (iFields != null && iFields.length % 2 != 0) + throw new IllegalArgumentException("Fields must be passed in pairs as name and value"); + + field(iFieldName, iFieldValue); + if (iFields != null && iFields.length > 0) + for (int i = 0; i < iFields.length; i += 2) { + field(iFields[i].toString(), iFields[i + 1]); + } + return this; + } + + /** + * Deprecated. Use fromMap(Map) instead.
          + * Fills a document passing the field names/values as a Map String,Object where the keys are the field names and the values are + * the field values. + * + * @see #fromMap(Map) + */ + @Deprecated + public ODocument fields(final Map iMap) { + return fromMap(iMap); + } + + /** + * Fills a document passing the field names/values as a Map String,Object where the keys are the field names and the values are + * the field values. It accepts also @rid for record id and @class for class name. + * + * @since 2.0 + */ + public ODocument fromMap(final Map iMap) { + if (iMap != null) { + for (Entry entry : iMap.entrySet()) + field(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Writes the field value forcing the type. This method sets the current document as dirty. + *

          + * if there's a schema definition for the specified field, the value will be converted to respect the schema definition if needed. + * if the type defined in the schema support less precision than the iPropertyValue provided, the iPropertyValue will be converted + * following the java casting rules with possible precision loss. + * + * @param iFieldName field name. If contains dots (.) the change is applied to the nested documents in chain. To disable this feature call + * {@link #setAllowChainedAccess(boolean)} to false. + * @param iPropertyValue field value. + * @param iFieldType Forced type (not auto-determined) + * @return The Record instance itself giving a "fluent interface". Useful to call multiple methods in chain. If the updated + * document is another document (using the dot (.) notation) then the document returned is the changed one or NULL if no + * document has been found in chain + */ + public ODocument field(String iFieldName, Object iPropertyValue, OType... iFieldType) { + if (iFieldName == null) + throw new IllegalArgumentException("Field is null"); + + if (iFieldName.isEmpty()) + throw new IllegalArgumentException("Field name is empty"); + + if (ODocumentHelper.ATTRIBUTE_CLASS.equals(iFieldName)) { + setClassName(iPropertyValue.toString()); + return this; + } else if (ODocumentHelper.ATTRIBUTE_RID.equals(iFieldName)) { + _recordId.fromString(iPropertyValue.toString()); + return this; + } else if (ODocumentHelper.ATTRIBUTE_VERSION.equals(iFieldName)) { + if (iPropertyValue != null) { + int v = _recordVersion; + + if (iPropertyValue instanceof Number) + v = ((Number) iPropertyValue).intValue(); + else + v = Integer.parseInt(iPropertyValue.toString()); + + _recordVersion = v; + } + return this; + } + + final int lastDotSep = _allowChainedAccess ? iFieldName.lastIndexOf('.') : -1; + final int lastArraySep = _allowChainedAccess ? iFieldName.lastIndexOf('[') : -1; + + final int lastSep = Math.max(lastArraySep, lastDotSep); + final boolean lastIsArray = lastArraySep > lastDotSep; + + if (lastSep > -1) { + // SUB PROPERTY GET 1 LEVEL BEFORE LAST + final Object subObject = field(iFieldName.substring(0, lastSep)); + if (subObject != null) { + final String subFieldName = lastIsArray ? iFieldName.substring(lastSep) : iFieldName.substring(lastSep + 1); + if (subObject instanceof ODocument) { + // SUB-DOCUMENT + ((ODocument) subObject).field(subFieldName, iPropertyValue); + return (ODocument) (((ODocument) subObject).isEmbedded() ? this : subObject); + } else if (subObject instanceof Map) { + // KEY/VALUE + ((Map) subObject).put(subFieldName, iPropertyValue); + } else if (OMultiValue.isMultiValue(subObject)) { + if ((subObject instanceof List || subObject.getClass().isArray()) && lastIsArray) { + // List // Array Type with a index subscript. + final int subFieldNameLen = subFieldName.length(); + + if (subFieldName.charAt(subFieldNameLen - 1) != ']') { + throw new IllegalArgumentException("Missed closing ']'"); + } + + final String indexPart = subFieldName.substring(1, subFieldNameLen - 1); + final Object indexPartObject = ODocumentHelper.getIndexPart(null, indexPart); + final String indexAsString = indexPartObject == null ? null : indexPartObject.toString(); + + try { + final int index = Integer.parseInt(indexAsString); + OMultiValue.setValue(subObject, iPropertyValue, index); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("List / array subscripts must resolve to integer values."); + } + } else { + // APPLY CHANGE TO ALL THE ITEM IN SUB-COLLECTION + for (Object subObjectItem : OMultiValue.getMultiValueIterable(subObject)) { + if (subObjectItem instanceof ODocument) { + // SUB-DOCUMENT, CHECK IF IT'S NOT LINKED + if (!((ODocument) subObjectItem).isEmbedded()) + throw new IllegalArgumentException("Property '" + iFieldName + + "' points to linked collection of items. You can only change embedded documents in this way"); + ((ODocument) subObjectItem).field(subFieldName, iPropertyValue); + } else if (subObjectItem instanceof Map) { + // KEY/VALUE + ((Map) subObjectItem).put(subFieldName, iPropertyValue); + } + } + } + return this; + } + } else + throw new IllegalArgumentException("Property '" + iFieldName.substring(0, lastSep) + + "' is null, is possible to set a value with dotted notation only on not null property"); + return null; + } + + iFieldName = checkFieldName(iFieldName); + + checkForLoading(); + checkForFields(); + + ODocumentEntry entry = _fields.get(iFieldName); + final boolean knownProperty; + final Object oldValue; + final OType oldType; + if (entry == null) { + entry = new ODocumentEntry(); + _fieldSize++; + _fields.put(iFieldName, entry); + entry.setCreated(true); + knownProperty = false; + oldValue = null; + oldType = null; + } else { + knownProperty = entry.exist(); + oldValue = entry.value; + oldType = entry.type; + } + OType fieldType = deriveFieldType(iFieldName, entry, iFieldType); + if (iPropertyValue != null && fieldType != null) { + iPropertyValue = ODocumentHelper.convertField(this, iFieldName, fieldType, null, iPropertyValue); + } else if (iPropertyValue instanceof Enum) + iPropertyValue = iPropertyValue.toString(); + + if (knownProperty) + // CHECK IF IS REALLY CHANGED + if (iPropertyValue == null) { + if (oldValue == null) + // BOTH NULL: UNCHANGED + return this; + } else { + + try { + if (iPropertyValue.equals(oldValue)) { + if (fieldType == oldType) { + if (!(iPropertyValue instanceof ORecordElement)) + // SAME BUT NOT TRACKABLE: SET THE RECORD AS DIRTY TO BE SURE IT'S SAVED + setDirty(); + + // SAVE VALUE: UNCHANGED + return this; + } + } else if (iPropertyValue instanceof byte[] && Arrays.equals((byte[]) iPropertyValue, (byte[]) oldValue)) { + // SAVE VALUE: UNCHANGED + return this; + } + } catch (Exception e) { + OLogManager.instance() + .warn(this, "Error on checking the value of property %s against the record %s", e, iFieldName, getIdentity()); + } + } + + if (oldValue instanceof ORidBag) { + final ORidBag ridBag = (ORidBag) oldValue; + ridBag.setOwner(null); + } else if (oldValue instanceof ODocument) { + ((ODocument) oldValue).removeOwner(this); + } + + if (oldValue instanceof OIdentifiable) { + unTrack((OIdentifiable) oldValue); + } + + if (iPropertyValue != null) { + if (iPropertyValue instanceof ODocument) { + if (OType.EMBEDDED.equals(fieldType)) { + final ODocument embeddedDocument = (ODocument) iPropertyValue; + ODocumentInternal.addOwner(embeddedDocument, this); + } else if (OType.LINK.equals(fieldType)) { + final ODocument embeddedDocument = (ODocument) iPropertyValue; + ODocumentInternal.removeOwner(embeddedDocument, this); + } + } + if (iPropertyValue instanceof OIdentifiable) { + track((OIdentifiable) iPropertyValue); + } + + if (iPropertyValue instanceof ORidBag) { + final ORidBag ridBag = (ORidBag) iPropertyValue; + ridBag.setOwner(null); // in order to avoid IllegalStateException when ridBag changes the owner (ODocument.merge) + ridBag.setOwner(this); + } + } + + if (oldType != fieldType && oldType != null) { + // can be made in a better way, but "keeping type" issue should be solved before + if (iPropertyValue == null || fieldType != null || oldType != OType.getTypeByValue(iPropertyValue)) + entry.type = fieldType; + } + removeCollectionChangeListener(entry, oldValue); + entry.value = iPropertyValue; + if (!entry.exist()) { + entry.setExist(true); + _fieldSize++; + } + addCollectionChangeListener(entry); + + if (_status != STATUS.UNMARSHALLING) { + setDirty(); + if (!entry.isChanged()) { + entry.original = oldValue; + entry.setChanged(true); + } + } + + return this; + } + + /** + * Removes a field. + */ + public Object removeField(final String iFieldName) { + checkForLoading(); + checkForFields(); + + if (ODocumentHelper.ATTRIBUTE_CLASS.equalsIgnoreCase(iFieldName)) { + setClassName(null); + } else if (ODocumentHelper.ATTRIBUTE_RID.equalsIgnoreCase(iFieldName)) { + _recordId = new ORecordId(); + } + + final ODocumentEntry entry = _fields.get(iFieldName); + if (entry == null) + return null; + Object oldValue = entry.value; + if (entry.exist() && _trackingChanges) { + // SAVE THE OLD VALUE IN A SEPARATE MAP + if (entry.original == null) + entry.original = entry.value; + entry.value = null; + entry.setExist(false); + entry.setChanged(true); + } else { + _fields.remove(iFieldName); + } + _fieldSize--; + + removeCollectionChangeListener(entry, oldValue); + if (oldValue instanceof OIdentifiable) + unTrack((OIdentifiable) oldValue); + if (oldValue instanceof ORidBag) + ((ORidBag) oldValue).setOwner(null); + setDirty(); + return oldValue; + } + + /** + * Merge current document with the document passed as parameter. If the field already exists then the conflicts are managed based + * on the value of the parameter 'iUpdateOnlyMode'. + * + * @param iOther Other ODocument instance to merge + * @param iUpdateOnlyMode if true, the other document properties will always be added or overwritten. If false, the missed properties in the + * "other" document will be removed by original document + * @param iMergeSingleItemsOfMultiValueFields If true, merges single items of multi field fields (collections, maps, arrays, etc) + * @return + */ + public ODocument merge(final ODocument iOther, boolean iUpdateOnlyMode, boolean iMergeSingleItemsOfMultiValueFields) { + iOther.checkForLoading(); + iOther.checkForFields(); + + if (_className == null && iOther.getImmutableSchemaClass() != null) + _className = iOther.getImmutableSchemaClass().getName(); + + return mergeMap(iOther._fields, iUpdateOnlyMode, iMergeSingleItemsOfMultiValueFields); + } + + /** + * Merge current document with the document passed as parameter. If the field already exists then the conflicts are managed based + * on the value of the parameter 'iUpdateOnlyMode'. + * + * @param iOther Other ODocument instance to merge + * @param iUpdateOnlyMode if true, the other document properties will always be added or overwritten. If false, the missed properties in the + * "other" document will be removed by original document + * @param iMergeSingleItemsOfMultiValueFields If true, merges single items of multi field fields (collections, maps, arrays, etc) + * @return + */ + public ODocument merge(final Map iOther, final boolean iUpdateOnlyMode, + boolean iMergeSingleItemsOfMultiValueFields) { + throw new UnsupportedOperationException(); + } + + /** + * Returns list of changed fields. There are two types of changes: + *

            + *
          1. Value of field itself was changed by calling of {@link #field(String, Object)} method for example.
          2. + *
          3. Internal state of field was changed but was not saved. This case currently is applicable for for collections only.
          4. + *
          + * + * @return List of fields, values of which were changed. + */ + public String[] getDirtyFields() { + if (_fields == null || _fields.isEmpty()) + return EMPTY_STRINGS; + + final Set dirtyFields = new HashSet(); + for (Entry entry : _fields.entrySet()) { + if (entry.getValue().isChanged() || entry.getValue().timeLine != null) + dirtyFields.add(entry.getKey()); + } + return dirtyFields.toArray(new String[dirtyFields.size()]); + } + + /** + * Returns the original value of a field before it has been changed. + * + * @param iFieldName Property name to retrieve the original value + */ + public Object getOriginalValue(final String iFieldName) { + if (_fields != null) { + ODocumentEntry entry = _fields.get(iFieldName); + if (entry != null) + return entry.original; + } + return null; + } + + public OMultiValueChangeTimeLine getCollectionTimeLine(final String iFieldName) { + ODocumentEntry entry = _fields != null ? _fields.get(iFieldName) : null; + return entry != null ? entry.timeLine : null; + } + + /** + * Returns the iterator fields + */ + public Iterator> iterator() { + checkForLoading(); + checkForFields(); + + if (_fields == null) + return OEmptyMapEntryIterator.INSTANCE; + + final Iterator> iterator = _fields.entrySet().iterator(); + return new Iterator>() { + private Entry current; + private boolean read = true; + + public boolean hasNext() { + while (iterator.hasNext()) { + current = iterator.next(); + if (current.getValue().exist()) { + read = false; + return true; + } + } + return false; + } + + public Entry next() { + if (read) + if (!hasNext()) { + // Look wrong but is correct, it need to fail if there isn't next. + iterator.next(); + } + final Entry toRet = new Entry() { + private Entry intern = current; + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return intern.getValue().value; + } + + @Override + public String getKey() { + return intern.getKey(); + } + }; + read = true; + return toRet; + } + + public void remove() { + + if (_trackingChanges) { + if (current.getValue().isChanged()) + current.getValue().original = current.getValue().value; + current.getValue().value = null; + current.getValue().setExist(false); + current.getValue().setChanged(true); + } else + iterator.remove(); + _fieldSize--; + removeCollectionChangeListener(current.getValue(), current.getValue().value); + } + }; + } + + /** + * Checks if a field exists. + * + * @return True if exists, otherwise false. + */ + public boolean containsField(final String iFieldName) { + if (iFieldName == null) + return false; + + checkForLoading(); + checkForFields(iFieldName); + ODocumentEntry entry = _fields.get(iFieldName); + return entry != null && entry.exist(); + } + + /** + * Returns true if the record has some owner. + */ + public boolean hasOwners() { + return _owners != null && !_owners.isEmpty(); + } + + @Override + public ORecordElement getOwner() { + if (_owners == null) + return null; + + for (WeakReference _owner : _owners) { + final ORecordElement e = _owner.get(); + if (e != null) + return e; + } + + return null; + } + + public Iterable getOwners() { + if (_owners == null) + return Collections.emptyList(); + + final List result = new ArrayList(); + for (WeakReference o : _owners) { + if (o.get() != null) + result.add(o.get()); + } + + return result; + } + + /** + * Propagates the dirty status to the owner, if any. This happens when the object is embedded in another one. + */ + @Override + public ORecordAbstract setDirty() { + if (_owners != null) { + // PROPAGATES TO THE OWNER + ORecordElement e; + for (WeakReference o : _owners) { + e = o.get(); + if (e != null) + e.setDirty(); + } + } else if (!isDirty()) + getDirtyManager().setDirty(this); + + // THIS IS IMPORTANT TO BE SURE THAT FIELDS ARE LOADED BEFORE IT'S TOO LATE AND THE RECORD _SOURCE IS NULL + checkForFields(); + + super.setDirty(); + + boolean addToChangedList = false; + + ORecordElement owner; + if (!isEmbedded()) + owner = this; + else { + owner = getOwner(); + while (owner != null && owner.getOwner() != null) { + owner = owner.getOwner(); + } + } + + if (owner instanceof ODocument && ((ODocument) owner).isTrackingChanges() && ((ODocument) owner).getIdentity().isPersistent()) + addToChangedList = true; + + if (addToChangedList) { + final ODatabaseDocument database = getDatabaseIfDefined(); + + if (database != null) { + final OTransaction transaction = database.getTransaction(); + + if (transaction instanceof OTransactionOptimistic) { + OTransactionOptimistic transactionOptimistic = (OTransactionOptimistic) transaction; + transactionOptimistic.addChangedDocument(this); + } + } + } + + return this; + } + + @Override + public void setDirtyNoChanged() { + if (_owners != null) { + // PROPAGATES TO THE OWNER + ORecordElement e; + for (WeakReference o : _owners) { + e = o.get(); + if (e != null) + e.setDirtyNoChanged(); + } + } + + getDirtyManager().setDirty(this); + + // THIS IS IMPORTANT TO BE SURE THAT FIELDS ARE LOADED BEFORE IT'S TOO LATE AND THE RECORD _SOURCE IS NULL + checkForFields(); + + super.setDirtyNoChanged(); + } + + @Override + public ODocument fromStream(final byte[] iRecordBuffer) { + removeAllCollectionChangeListeners(); + + _fields = null; + _fieldSize = 0; + _contentChanged = false; + _schema = null; + fetchSchemaIfCan(); + super.fromStream(iRecordBuffer); + + if (!_lazyLoad) { + checkForLoading(); + checkForFields(); + } + + return this; + } + + /** + * Returns the forced field type if any. + * + * @param iFieldName name of field to check + */ + public OType fieldType(final String iFieldName) { + checkForLoading(); + checkForFields(iFieldName); + + ODocumentEntry entry = _fields.get(iFieldName); + if (entry != null) + return entry.type; + + return null; + } + + @Override + public ODocument unload() { + super.unload(); + internalReset(); + return this; + } + + /** + *

          + * Clears all the field values and types. Clears only record content, but saves its identity. + *

          + *

          + *

          + * The following code will clear all data from specified document. + *

          + * + * doc.clear(); + * doc.save(); + * + * + * @return this + * @see #reset() + */ + @Override + public ODocument clear() { + super.clear(); + internalReset(); + _owners = null; + return this; + } + + /** + *

          + * Resets the record values and class type to being reused. It's like you create a ODocument from scratch. This method is handy + * when you want to insert a bunch of documents and don't want to strain GC. + *

          + *

          + *

          + * The following code will create a new document in database. + *

          + * + * doc.clear(); + * doc.save(); + * + *

          + *

          + * IMPORTANT! This can be used only if no transactions are begun. + *

          + * + * @return this + * @throws IllegalStateException if transaction is begun. + * @see #clear() + */ + @Override + public ODocument reset() { + ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null && db.getTransaction().isActive()) + throw new IllegalStateException("Cannot reset documents during a transaction. Create a new one each time"); + + super.reset(); + + _className = null; + _immutableClazz = null; + _immutableSchemaVersion = -1; + + internalReset(); + + _owners = null; + return this; + } + + /** + * Rollbacks changes to the loaded version without reloading the document. Works only if tracking changes is enabled @see + * {@link #isTrackingChanges()} and {@link #setTrackingChanges(boolean)} methods. + */ + public ODocument undo() { + if (!_trackingChanges) + throw new OConfigurationException("Cannot undo the document because tracking of changes is disabled"); + + if (_fields != null) { + Iterator> vals = _fields.entrySet().iterator(); + while (vals.hasNext()) { + Entry next = vals.next(); + ODocumentEntry val = next.getValue(); + if (val.created) { + vals.remove(); + } else if (val.changed) { + val.value = val.original; + val.changed = false; + val.original = null; + val.exist = true; + } + } + _fieldSize = _fields.size(); + } + + return this; + } + + public ODocument undo(final String field) { + if (!_trackingChanges) + throw new OConfigurationException("Cannot undo the document because tracking of changes is disabled"); + + if (_fields != null) { + final ODocumentEntry value = _fields.get(field); + if (value != null) { + if (value.created) { + _fields.remove(field); + } + if (value.changed) { + value.value = value.original; + value.original = null; + value.changed = false; + value.exist = true; + } + } + } + return this; + } + + public boolean isLazyLoad() { + return _lazyLoad; + } + + public void setLazyLoad(final boolean iLazyLoad) { + this._lazyLoad = iLazyLoad; + checkForFields(); + + if (_fields != null) { + // PROPAGATE LAZINESS TO THE FIELDS + for (Entry field : _fields.entrySet()) { + if (field.getValue().value instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) field.getValue().value).setAutoConvertToRecord(false); + } + } + } + + public boolean isTrackingChanges() { + return _trackingChanges; + } + + /** + * Enabled or disabled the tracking of changes in the document. This is needed by some triggers like + * {@link com.orientechnologies.orient.core.index.OClassIndexManager} to determine what fields are changed to update indexes. + * + * @param iTrackingChanges True to enable it, otherwise false + * @return this + */ + public ODocument setTrackingChanges(final boolean iTrackingChanges) { + this._trackingChanges = iTrackingChanges; + if (!iTrackingChanges && _fields != null) { + // FREE RESOURCES + Iterator> iter = _fields.entrySet().iterator(); + while (iter.hasNext()) { + Entry cur = iter.next(); + if (!cur.getValue().exist()) + iter.remove(); + else { + cur.getValue().setCreated(false); + cur.getValue().setChanged(false); + cur.getValue().original = null; + cur.getValue().timeLine = null; + } + } + removeAllCollectionChangeListeners(); + } else { + addAllMultiValueChangeListeners(); + } + return this; + } + + protected void clearTrackData() { + if (_fields != null) { + // FREE RESOURCES + Iterator> iter = _fields.entrySet().iterator(); + while (iter.hasNext()) { + Entry cur = iter.next(); + if (!cur.getValue().exist()) + iter.remove(); + else { + cur.getValue().setCreated(false); + cur.getValue().setChanged(false); + cur.getValue().original = null; + cur.getValue().timeLine = null; + + if (cur.getValue().changeListener == null && cur.getValue().value instanceof OTrackedMultiValue) { + addCollectionChangeListener(cur.getValue()); + } + } + } + } + } + + public boolean isOrdered() { + return _ordered; + } + + public ODocument setOrdered(final boolean iOrdered) { + this._ordered = iOrdered; + return this; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) + return false; + + return this == obj || _recordId.isValid(); + } + + @Override + public int hashCode() { + if (_recordId.isValid()) + return super.hashCode(); + + return System.identityHashCode(this); + } + + /** + * Returns the number of fields in memory. + */ + public int fields() { + checkForLoading(); + checkForFields(); + return _fieldSize; + } + + public boolean isEmpty() { + checkForLoading(); + checkForFields(); + return _fields == null || _fields.isEmpty(); + } + + @Override + public ODocument fromJSON(final String iSource, final String iOptions) { + return (ODocument) super.fromJSON(iSource, iOptions); + } + + @Override + public ODocument fromJSON(final String iSource) { + return (ODocument) super.fromJSON(iSource); + } + + @Override + public ODocument fromJSON(final InputStream iContentResult) throws IOException { + return (ODocument) super.fromJSON(iContentResult); + } + + @Override + public ODocument fromJSON(final String iSource, final boolean needReload) { + return (ODocument) super.fromJSON(iSource, needReload); + } + + public boolean isEmbedded() { + return _owners != null && !_owners.isEmpty(); + } + + /** + * Sets the field type. This overrides the schema property settings if any. + * + * @param iFieldName Field name + * @param iFieldType Type to set between OType enumeration values + */ + public ODocument setFieldType(final String iFieldName, final OType iFieldType) { + checkForLoading(); + checkForFields(iFieldName); + if (iFieldType != null) { + if (_fields == null) + _fields = _ordered ? new LinkedHashMap() : new HashMap(); + // SET THE FORCED TYPE + ODocumentEntry entry = getOrCreate(iFieldName); + if (entry.type != iFieldType) + field(iFieldName, field(iFieldName), iFieldType); + } else if (_fields != null) { + // REMOVE THE FIELD TYPE + ODocumentEntry entry = _fields.get(iFieldName); + if (entry != null) + // EMPTY: OPTIMIZE IT BY REMOVING THE ENTIRE MAP + entry.type = null; + } + return this; + } + + @Override + public ODocument save() { + return (ODocument) save(null, false); + } + + @Override + public ODocument save(final String iClusterName) { + return (ODocument) save(iClusterName, false); + } + + public ORecordAbstract save(final String iClusterName, final boolean forceCreate) { + return getDatabase().save(this, iClusterName, ODatabase.OPERATION_MODE.SYNCHRONOUS, forceCreate, null, null); + } + + /* + * Initializes the object if has been unserialized + */ + public boolean deserializeFields(final String... iFields) { + if (_source == null) + // ALREADY UNMARSHALLED OR JUST EMPTY + return true; + + if (iFields != null && iFields.length > 0) { + // EXTRACT REAL FIELD NAMES + for (int i = 0; i < iFields.length; ++i) { + final String f = iFields[i]; + if (f != null && !f.startsWith("@")) { + int pos1 = f.indexOf('['); + int pos2 = f.indexOf('.'); + if (pos1 > -1 || pos2 > -1) { + int pos = pos1 > -1 ? pos1 : pos2; + if (pos2 > -1 && pos2 < pos) + pos = pos2; + + // REPLACE THE FIELD NAME + iFields[i] = f.substring(0, pos); + } + } + } + + // CHECK IF HAS BEEN ALREADY UNMARSHALLED + if (_fields != null && !_fields.isEmpty()) { + boolean allFound = true; + for (String f : iFields) + if (f != null && !f.startsWith("@") && !_fields.containsKey(f)) { + allFound = false; + break; + } + + if (allFound) + // ALL THE REQUESTED FIELDS HAVE BEEN LOADED BEFORE AND AVAILABLE, AVOID UNMARSHALLIGN + return true; + } + } + + if (_recordFormat == null) + setup(); + + _status = ORecordElement.STATUS.UNMARSHALLING; + try { + _recordFormat.fromStream(_source, this, iFields); + } finally { + _status = ORecordElement.STATUS.LOADED; + } + + if (iFields != null && iFields.length > 0) { + for (String field : iFields) { + if (field != null && field.startsWith("@")) + // ATTRIBUTE + return true; + } + + // PARTIAL UNMARSHALLING + if (_fields != null && !_fields.isEmpty()) + for (String f : iFields) + if (f != null && _fields.containsKey(f)) + return true; + + // NO FIELDS FOUND + return false; + } else if (_source != null) + // FULL UNMARSHALLING + _source = null; + + return true; + } + + @Override + public void writeExternal(final ObjectOutput stream) throws IOException { + ORecordSerializer serializer = ORecordSerializerFactory.instance().getFormat(ORecordSerializerNetwork.NAME); + final byte[] idBuffer = _recordId.toStream(); + stream.writeInt(-1); + stream.writeInt(idBuffer.length); + stream.write(idBuffer); + stream.writeInt(_recordVersion); + + final byte[] content = serializer.toStream(this, false); + stream.writeInt(content.length); + stream.write(content); + + stream.writeBoolean(_dirty); + stream.writeObject(serializer.toString()); + } + + @Override + public void readExternal(final ObjectInput stream) throws IOException, ClassNotFoundException { + int i = stream.readInt(); + int size; + if (i < 0) + size = stream.readInt(); + else + size = i; + final byte[] idBuffer = new byte[size]; + stream.readFully(idBuffer); + _recordId.fromStream(idBuffer); + + _recordVersion = stream.readInt(); + + final int len = stream.readInt(); + final byte[] content = new byte[len]; + stream.readFully(content); + + _dirty = stream.readBoolean(); + + ORecordSerializer serializer = _recordFormat; + if (i < 0) { + final String str = (String) stream.readObject(); + // TODO: WHEN TO USE THE SERIALIZER? + serializer = ORecordSerializerFactory.instance().getFormat(str); + } + + _status = ORecordElement.STATUS.UNMARSHALLING; + try { + serializer.fromStream(content, this, null); + } finally { + _status = ORecordElement.STATUS.LOADED; + } + } + + /** + * Returns the behavior of field() methods allowing access to the sub documents with dot notation ('.'). Default is true. Set it + * to false if you allow to store properties with the dot. + */ + public boolean isAllowChainedAccess() { + return _allowChainedAccess; + } + + /** + * Change the behavior of field() methods allowing access to the sub documents with dot notation ('.'). Default is true. Set it to + * false if you allow to store properties with the dot. + */ + public ODocument setAllowChainedAccess(final boolean _allowChainedAccess) { + this._allowChainedAccess = _allowChainedAccess; + return this; + } + + public void setClassNameIfExists(final String iClassName) { + _immutableClazz = null; + _immutableSchemaVersion = -1; + + _className = iClassName; + + if (iClassName == null) { + return; + } + + final ODatabaseDocument db = getDatabaseIfDefined(); + if (db != null) { + final OClass _clazz = ((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot().getClass(iClassName); + if (_clazz != null) { + _className = _clazz.getName(); + convertFieldsToClass(_clazz); + } + } + } + + public OClass getSchemaClass() { + if (_className == null) + fetchClassName(); + + if (_className == null) + return null; + + final ODatabaseDocument databaseRecord = getDatabaseIfDefined(); + if (databaseRecord != null) + return databaseRecord.getMetadata().getSchema().getClass(_className); + + return null; + } + + public String getClassName() { + if (_className == null) + fetchClassName(); + + return _className; + } + + public void setClassName(final String className) { + _immutableClazz = null; + _immutableSchemaVersion = -1; + + _className = className; + + if (className == null) { + return; + } + + final ODatabaseDocument db = getDatabaseIfDefined(); + if (db != null) { + OMetadataInternal metadata = (OMetadataInternal) db.getMetadata(); + this._immutableClazz = (OImmutableClass) metadata.getImmutableSchemaSnapshot().getClass(className); + OClass clazz; + if (this._immutableClazz != null) { + clazz = this._immutableClazz; + } else { + clazz = metadata.getSchema().getOrCreateClass(className); + } + if (clazz != null) { + _className = clazz.getName(); + convertFieldsToClass(clazz); + } + } + } + + /** + * Validates the record following the declared constraints defined in schema such as mandatory, notNull, min, max, regexp, etc. If + * the schema is not defined for the current class or there are not constraints then the validation is ignored. + * + * @throws OValidationException if the document breaks some validation constraints defined in the schema + * @see OProperty + */ + public void validate() throws OValidationException { + checkForLoading(); + checkForFields(); + + autoConvertValues(); + + if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && !getDatabase().isValidationEnabled()) + return; + + final OImmutableClass immutableSchemaClass = getImmutableSchemaClass(); + if (immutableSchemaClass != null) { + if (immutableSchemaClass.isStrictMode()) { + // CHECK IF ALL FIELDS ARE DEFINED + for (String f : fieldNames()) { + if (immutableSchemaClass.getProperty(f) == null) + throw new OValidationException( + "Found additional field '" + f + "'. It cannot be added because the schema class '" + immutableSchemaClass.getName() + + "' is defined as STRICT"); + } + } + + for (OProperty p : immutableSchemaClass.properties()) { + validateField(this, (OImmutableProperty) p); + } + } + } + + protected String toString(Set inspected) { + if (inspected.contains(this)) + return ""; + else + inspected.add(this); + + final boolean saveDirtyStatus = _dirty; + final boolean oldUpdateContent = _contentChanged; + + try { + final StringBuilder buffer = new StringBuilder(128); + + checkForFields(); + + final ODatabaseDocument db = getDatabaseIfDefined(); + if (db != null && !db.isClosed()) { + final String clsName = getClassName(); + if (clsName != null) + buffer.append(clsName); + } + + if (_recordId != null) { + if (_recordId.isValid()) + buffer.append(_recordId); + } + + boolean first = true; + for (Entry f : _fields.entrySet()) { + buffer.append(first ? '{' : ','); + buffer.append(f.getKey()); + buffer.append(':'); + if (f.getValue().value == null) + buffer.append("null"); + else if (f.getValue().value instanceof Collection || f.getValue().value instanceof Map || f.getValue().value + .getClass().isArray()) { + buffer.append('['); + buffer.append(OMultiValue.getSize(f.getValue().value)); + buffer.append(']'); + } else if (f.getValue().value instanceof ORecord) { + final ORecord record = (ORecord) f.getValue().value; + + if (record.getIdentity().isValid()) + record.getIdentity().toString(buffer); + else if (record instanceof ODocument) + buffer.append(((ODocument) record).toString(inspected)); + else + buffer.append(record.toString()); + } else + buffer.append(f.getValue().value); + + if (first) + first = false; + } + if (!first) + buffer.append('}'); + + if (_recordId != null && _recordId.isValid()) { + buffer.append(" v"); + buffer.append(_recordVersion); + } + + return buffer.toString(); + } finally { + _dirty = saveDirtyStatus; + _contentChanged = oldUpdateContent; + } + } + + protected ODocument mergeMap(final Map iOther, final boolean iUpdateOnlyMode, + boolean iMergeSingleItemsOfMultiValueFields) { + checkForLoading(); + checkForFields(); + + _source = null; + + for (String f : iOther.keySet()) { + ODocumentEntry docEntry = iOther.get(f); + if (!docEntry.exist()) { + continue; + } + final Object otherValue = docEntry.value; + + ODocumentEntry curValue = _fields.get(f); + + if (curValue != null && curValue.exist()) { + final Object value = curValue.value; + if (iMergeSingleItemsOfMultiValueFields) { + if (value instanceof Map) { + final Map map = (Map) value; + final Map otherMap = (Map) otherValue; + + for (Entry entry : otherMap.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + continue; + } else if (OMultiValue.isMultiValue(value) && !(value instanceof ORidBag)) { + for (Object item : OMultiValue.getMultiValueIterable(otherValue)) { + if (!OMultiValue.contains(value, item)) + OMultiValue.add(value, item); + } + + // JUMP RAW REPLACE + continue; + } + } + boolean bagsMerged = false; + if (value instanceof ORidBag && otherValue instanceof ORidBag) + bagsMerged = ((ORidBag) value).tryMerge((ORidBag) otherValue, iMergeSingleItemsOfMultiValueFields); + + if (!bagsMerged && (value != null && !value.equals(otherValue)) || (value == null && otherValue != null)) { + field(f, otherValue); + } + } else { + field(f, otherValue); + } + } + + if (!iUpdateOnlyMode) { + // REMOVE PROPERTIES NOT FOUND IN OTHER DOC + for (String f : fieldNames()) + if (!iOther.containsKey(f) || !iOther.get(f).exist()) + removeField(f); + } + + return this; + } + + @Override + protected ORecordAbstract fill(final ORID iRid, final int iVersion, final byte[] iBuffer, final boolean iDirty) { + _schema = null; + fetchSchemaIfCan(); + return super.fill(iRid, iVersion, iBuffer, iDirty); + } + + @Override + protected void clearSource() { + super.clearSource(); + _schema = null; + } + + protected OGlobalProperty getGlobalPropertyById(int id) { + if (_schema == null) { + OMetadataInternal metadata = (OMetadataInternal) getDatabase().getMetadata(); + _schema = metadata.getImmutableSchemaSnapshot(); + } + OGlobalProperty prop = _schema.getGlobalPropertyById(id); + if (prop == null) { + ODatabaseDocument db = getDatabase(); + if (db == null || db.isClosed()) + throw new ODatabaseException( + "Cannot unmarshall the document because no database is active, use detach for use the document outside the database session scope"); + OMetadataInternal metadata = (OMetadataInternal) db.getMetadata(); + if (metadata.getImmutableSchemaSnapshot() != null) + metadata.clearThreadLocalSchemaSnapshot(); + metadata.reload(); + metadata.makeThreadLocalSchemaSnapshot(); + _schema = metadata.getImmutableSchemaSnapshot(); + prop = _schema.getGlobalPropertyById(id); + } + return prop; + } + + protected void fillClassIfNeed(final String iClassName) { + if (this._className == null) { + _immutableClazz = null; + _immutableSchemaVersion = -1; + _className = iClassName; + } + + } + + protected OImmutableClass getImmutableSchemaClass() { + if (_immutableClazz == null) { + if (_className == null) + fetchClassName(); + final ODatabaseDocument databaseRecord = getDatabaseIfDefined(); + + if (databaseRecord != null && !databaseRecord.isClosed()) { + final OSchema immutableSchema = ((OMetadataInternal) databaseRecord.getMetadata()).getImmutableSchemaSnapshot(); + if (immutableSchema == null) + return null; + _immutableSchemaVersion = immutableSchema.getVersion(); + _immutableClazz = (OImmutableClass) immutableSchema.getClass(_className); + } + } + + return _immutableClazz; + } + + protected void rawField(final String iFieldName, final Object iFieldValue, final OType iFieldType) { + if (_fields == null) + _fields = _ordered ? new LinkedHashMap() : new HashMap(); + + ODocumentEntry entry = getOrCreate(iFieldName); + removeCollectionChangeListener(entry, entry.value); + entry.value = iFieldValue; + entry.type = iFieldType; + addCollectionChangeListener(entry); + if (iFieldValue instanceof OIdentifiable && !((OIdentifiable) iFieldValue).getIdentity().isPersistent()) + track((OIdentifiable) iFieldValue); + } + + protected ODocumentEntry getOrCreate(String key) { + ODocumentEntry entry = _fields.get(key); + if (entry == null) { + entry = new ODocumentEntry(); + _fieldSize++; + _fields.put(key, entry); + } + return entry; + } + + protected boolean rawContainsField(final String iFiledName) { + return _fields != null && _fields.containsKey(iFiledName); + } + + protected void autoConvertValues() { + OClass clazz = getImmutableSchemaClass(); + if (clazz != null) { + for (OProperty prop : clazz.properties()) { + OType type = prop.getType(); + OType linkedType = prop.getLinkedType(); + OClass linkedClass = prop.getLinkedClass(); + if(type == OType.EMBEDDED && linkedClass!=null){ + convertToEmbeddedType(prop); + continue; + } + if (linkedType == null) + continue; + final ODocumentEntry entry = _fields.get(prop.getName()); + if (entry == null) + continue; + if (!entry.created && !entry.changed) + continue; + Object value = entry.value; + if (value == null) + continue; + try { + if (type == OType.EMBEDDEDLIST) { + OTrackedList list = new OTrackedList(this); + Collection values = (Collection) value; + for (Object object : values) { + list.add(OType.convert(object, linkedType.getDefaultJavaType())); + } + entry.value = list; + replaceListenerOnAutoconvert(entry, value); + } else if (type == OType.EMBEDDEDMAP) { + Map map = new OTrackedMap(this); + Map values = (Map) value; + for (Entry object : values.entrySet()) { + map.put(object.getKey(), OType.convert(object.getValue(), linkedType.getDefaultJavaType())); + } + entry.value = map; + replaceListenerOnAutoconvert(entry, value); + } else if (type == OType.EMBEDDEDSET) { + Set set = new OTrackedSet(this); + Collection values = (Collection) value; + for (Object object : values) { + set.add(OType.convert(object, linkedType.getDefaultJavaType())); + } + entry.value = set; + replaceListenerOnAutoconvert(entry, value); + } + } catch (Exception e) { + throw OException + .wrapException(new OValidationException("impossible to convert value of field \"" + prop.getName() + "\""), e); + } + } + } + } + + private void convertToEmbeddedType(OProperty prop) { + final ODocumentEntry entry = _fields.get(prop.getName()); + OClass linkedClass = prop.getLinkedClass(); + if (entry == null || linkedClass == null) { + return; + } + if (!entry.created && !entry.changed) { + return; + } + Object value = entry.value; + if (value == null) { + return; + } + try { + if (value instanceof ODocument) { + OClass docClass = ((ODocument) value).getSchemaClass(); + if (docClass == null) { + ((ODocument) value).setClass(linkedClass); + } else if (!docClass.isSubClassOf(linkedClass)) { + throw new OValidationException( + "impossible to convert value of field \"" + prop.getName() + "\", incompatible with " + linkedClass); + } + } else if (value instanceof Map) { + removeCollectionChangeListener(entry, value); + ODocument newValue = new ODocument(linkedClass); + newValue.fromMap((Map) value); + entry.value = newValue; + newValue.addOwner(this); + } else { + throw new OValidationException("impossible to convert value of field \"" + prop.getName() + "\""); + } + + } catch (Exception e) { + throw OException + .wrapException(new OValidationException("impossible to convert value of field \"" + prop.getName() + "\""), e); + } + } + + private void replaceListenerOnAutoconvert(final ODocumentEntry entry, Object oldValue) { + if (entry.changeListener != null) { + // A listener was there, remove on the old value add it to the new value. + final OTrackedMultiValue oldMultiValue = (OTrackedMultiValue) oldValue; + oldMultiValue.removeRecordChangeListener(entry.changeListener); + ((OTrackedMultiValue) entry.value).addChangeListener(entry.changeListener); + } else { + // no listener was there add it only to the new value + final OSimpleMultiValueChangeListener listener = new OSimpleMultiValueChangeListener(this, + entry); + ((OTrackedMultiValue) entry.value).addChangeListener(listener); + entry.changeListener = listener; + } + } + + protected byte[] toStream(final boolean iOnlyDelta) { + STATUS prev = _status; + _status = STATUS.MARSHALLING; + try { + if (_source == null) + _source = _recordFormat.toStream(this, iOnlyDelta); + } finally { + _status = prev; + } + invokeListenerEvent(ORecordListener.EVENT.MARSHALL); + + return _source; + } + + /** + * Internal. + */ + protected byte getRecordType() { + return RECORD_TYPE; + } + + /** + * Internal. + */ + protected void addOwner(final ORecordElement iOwner) { + if (iOwner == null) + return; + if (_owners == null) { + _owners = new ArrayList>(); + if (_dirtyManager != null && this.getIdentity().isNew()) + _dirtyManager.removeNew(this); + } + + boolean found = false; + Iterator> ref = _owners.iterator(); + while (ref.hasNext()) { + final ORecordElement e = ref.next().get(); + if (e == iOwner) { + found = true; + break; + } else if (e == null) + ref.remove(); + } + + if (!found) + this._owners.add(new WeakReference(iOwner)); + + } + + protected void removeOwner(final ORecordElement iRecordElement) { + if (_owners != null) { + // PROPAGATES TO THE OWNER + ORecordElement e; + for (int i = 0; i < _owners.size(); ++i) { + e = _owners.get(i).get(); + if (e == iRecordElement) { + _owners.remove(i); + break; + } + } + } + } + + /** + * Converts all non-tracked collections implementations contained in document fields to tracked ones. + * + * @see OTrackedMultiValue + */ + protected void convertAllMultiValuesToTrackedVersions() { + if (_fields == null) + return; + for (Map.Entry fieldEntry : _fields.entrySet()) { + final Object fieldValue = fieldEntry.getValue().value; + if (!(fieldValue instanceof Collection) && !(fieldValue instanceof Map) && !(fieldValue instanceof ODocument)) + continue; + if (addCollectionChangeListener(fieldEntry.getValue())) { + if (fieldEntry.getValue().timeLine != null && !fieldEntry.getValue().timeLine.getMultiValueChangeEvents().isEmpty()) { + checkTimelineTrackable(fieldEntry.getValue().timeLine, (OTrackedMultiValue) fieldEntry.getValue().value); + } + continue; + } + + if (fieldValue instanceof ODocument && ((ODocument) fieldValue).isEmbedded()) { + ((ODocument) fieldValue).convertAllMultiValuesToTrackedVersions(); + continue; + } + + OType fieldType = fieldEntry.getValue().type; + if (fieldType == null) { + OClass _clazz = getImmutableSchemaClass(); + if (_clazz != null) { + final OProperty prop = _clazz.getProperty(fieldEntry.getKey()); + fieldType = prop != null ? prop.getType() : null; + } + } + if (fieldType == null) + fieldType = OType.getTypeByValue(fieldValue); + + Object newValue = null; + switch (fieldType) { + case EMBEDDEDLIST: + if (fieldValue instanceof List) { + newValue = new OTrackedList(this); + fillTrackedCollection((Collection) newValue, (Collection) fieldValue); + } + break; + case EMBEDDEDSET: + if (fieldValue instanceof Set) { + newValue = new OTrackedSet(this); + fillTrackedCollection((Collection) newValue, (Collection) fieldValue); + } + break; + case EMBEDDEDMAP: + if (fieldValue instanceof Map) { + newValue = new OTrackedMap(this); + fillTrackedMap((Map) newValue, (Map) fieldValue); + } + break; + case LINKLIST: + if (fieldValue instanceof List) + newValue = new ORecordLazyList(this, (Collection) fieldValue); + break; + case LINKSET: + if (fieldValue instanceof Set) + newValue = new ORecordLazySet(this, (Collection) fieldValue); + break; + case LINKMAP: + if (fieldValue instanceof Map) + newValue = new ORecordLazyMap(this, (Map) fieldValue); + break; + case LINKBAG: + if (fieldValue instanceof Collection) { + ORidBag bag = new ORidBag(); + bag.setOwner(this); + bag.addAll((Collection) fieldValue); + newValue = bag; + } + break; + default: + break; + } + + if (newValue != null) { + addCollectionChangeListener(fieldEntry.getValue()); + fieldEntry.getValue().value = newValue; + if (fieldType == OType.LINKSET || fieldType == OType.LINKLIST) { + boolean pre = ((OAutoConvertToRecord) newValue).isAutoConvertToRecord(); + ((OAutoConvertToRecord) newValue).setAutoConvertToRecord(false); + for (OIdentifiable rec : (Collection) newValue) { + if (rec instanceof ODocument) + ((ODocument) rec).convertAllMultiValuesToTrackedVersions(); + } + ((OAutoConvertToRecord) newValue).setAutoConvertToRecord(pre); + } else if (fieldType == OType.LINKMAP) { + boolean pre = ((OAutoConvertToRecord) newValue).isAutoConvertToRecord(); + ((OAutoConvertToRecord) newValue).setAutoConvertToRecord(false); + for (OIdentifiable rec : (Collection) ((Map) newValue).values()) { + if (rec instanceof ODocument) + ((ODocument) rec).convertAllMultiValuesToTrackedVersions(); + } + ((OAutoConvertToRecord) newValue).setAutoConvertToRecord(pre); + } + } + } + + } + + private void checkTimelineTrackable(OMultiValueChangeTimeLine timeLine, OTrackedMultiValue origin) { + List> events = timeLine.getMultiValueChangeEvents(); + for (OMultiValueChangeEvent event : events) { + Object value = event.getValue(); + if (event.getChangeType() == OMultiValueChangeEvent.OChangeType.ADD && !(value instanceof OTrackedMultiValue)) { + if (value instanceof Collection) { + Collection newCollection = value instanceof List ? new OTrackedList(this) : new OTrackedSet(this); + fillTrackedCollection(newCollection, (Collection) value); + origin.replace(event, newCollection); + } else if (value instanceof Map) { + Map newMap = new OTrackedMap(this); + fillTrackedMap(newMap, (Map) value); + origin.replace(event, newMap); + } + } else if (event.getChangeType() == OMultiValueChangeEvent.OChangeType.NESTED) { + OMultiValueChangeTimeLine nestedTimeline = ((ONestedMultiValueChangeEvent) event).getTimeLine(); + if (nestedTimeline != null) + checkTimelineTrackable(nestedTimeline, (OTrackedMultiValue) value); + } + } + } + + private void fillTrackedCollection(Collection dest, Collection source) { + for (Object cur : source) { + if (cur instanceof ODocument) + ((ODocument) cur).convertAllMultiValuesToTrackedVersions(); + else if (cur instanceof List) { + List newList = new OTrackedList(this); + fillTrackedCollection(newList, (Collection) cur); + cur = newList; + } else if (cur instanceof Set) { + Set newSet = new OTrackedSet(this); + fillTrackedCollection(newSet, (Collection) cur); + cur = newSet; + } else if (cur instanceof Map) { + Map newMap = new OTrackedMap(this); + fillTrackedMap(newMap, (Map) cur); + cur = newMap; + } + dest.add(cur); + } + } + + private void fillTrackedMap(Map dest, Map source) { + for (Entry cur : source.entrySet()) { + Object value = cur.getValue(); + if (value instanceof ODocument) + ((ODocument) value).convertAllMultiValuesToTrackedVersions(); + else if (cur.getValue() instanceof List) { + List newList = new OTrackedList(this); + fillTrackedCollection(newList, (Collection) value); + value = newList; + } else if (value instanceof Set) { + Set newSet = new OTrackedSet(this); + fillTrackedCollection(newSet, (Collection) value); + value = newSet; + } else if (value instanceof Map) { + Map newMap = new OTrackedMap(this); + fillTrackedMap(newMap, (Map) value); + value = newMap; + } + dest.put(cur.getKey(), value); + } + } + + protected void internalReset() { + removeAllCollectionChangeListeners(); + + if (_fields != null) + _fields.clear(); + _fieldSize = 0; + + } + + protected boolean checkForFields(final String... iFields) { + if (_fields == null) + _fields = _ordered ? new LinkedHashMap() : new HashMap(); + + if (_status == ORecordElement.STATUS.LOADED && _source != null) + // POPULATE FIELDS LAZY + return deserializeFields(iFields); + + return true; + } + + /** + * Internal. + */ + @Override + protected void setup() { + super.setup(); + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + _recordFormat = db.getSerializer(); + + if (_recordFormat == null) + // GET THE DEFAULT ONE + _recordFormat = ODatabaseDocumentTx.getDefaultSerializer(); + } + + protected String checkFieldName(final String iFieldName) { + final Character c = OSchemaShared.checkFieldNameIfValid(iFieldName); + if (c != null) + throw new IllegalArgumentException("Invalid field name '" + iFieldName + "'. Character '" + c + "' is invalid"); + + return iFieldName; + } + + protected void setClass(final OClass iClass) { + if (iClass != null && iClass.isAbstract()) + throw new OSchemaException("Cannot create a document of the abstract class '" + iClass + "'"); + + if (iClass == null) + _className = null; + else + _className = iClass.getName(); + + _immutableClazz = null; + _immutableSchemaVersion = -1; + if (iClass != null) + convertFieldsToClass(iClass); + } + + protected Set> getRawEntries() { + checkForFields(); + return _fields == null ? new HashSet>() : _fields.entrySet(); + } + + private void fetchSchemaIfCan() { + if (_schema == null) { + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null && !db.isClosed()) { + OMetadataInternal metadata = (OMetadataInternal) db.getMetadata(); + _schema = metadata.getImmutableSchemaSnapshot(); + } + } + } + + private void fetchClassName() { + final ODatabaseDocumentInternal database = getDatabaseIfDefinedInternal(); + if (database != null && database.getStorageVersions() != null && database.getStorageVersions() + .classesAreDetectedByClusterId()) { + if (_recordId.getClusterId() < 0) { + checkForLoading(); + checkForFields(ODocumentHelper.ATTRIBUTE_CLASS); + } else { + final OSchema schema = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot(); + if (schema != null) { + OClass _clazz = schema.getClassByClusterId(_recordId.getClusterId()); + if (_clazz != null) + _className = _clazz.getName(); + } + } + } else { + // CLASS NOT FOUND: CHECK IF NEED LOADING AND UNMARSHALLING + checkForLoading(); + checkForFields(ODocumentHelper.ATTRIBUTE_CLASS); + } + } + + protected void autoConvertFieldsToClass(final ODatabaseDocumentInternal database) { + if (_className != null) { + OClass klazz = database.getMetadata().getImmutableSchemaSnapshot().getClass(_className); + convertFieldsToClass(klazz); + } + } + /** + * Checks and convert the field of the document matching the types specified by the class. + **/ + private void convertFieldsToClass(final OClass _clazz) { + for (OProperty prop : _clazz.properties()) { + ODocumentEntry entry = _fields != null ? _fields.get(prop.getName()) : null; + if (entry != null && entry.exist()) { + if (entry.type == null || entry.type != prop.getType()) { + field(prop.getName(), entry.value, prop.getType()); + } + } else { + String defValue = prop.getDefaultValue(); + if (defValue != null && defValue.length() > 0 && !containsField(prop.getName())) { + Object curFieldValue = OSQLHelper.parseDefaultValue(this, defValue); + Object fieldValue = ODocumentHelper.convertField(this, prop.getName(), prop.getType(), null, curFieldValue); + rawField(prop.getName(), fieldValue, prop.getType()); + } + } + } + } + + private OType deriveFieldType(String iFieldName, ODocumentEntry entry, OType[] iFieldType) { + OType fieldType; + + if (iFieldType != null && iFieldType.length == 1) { + entry.type = iFieldType[0]; + fieldType = iFieldType[0]; + } else + fieldType = null; + + OClass _clazz = getImmutableSchemaClass(); + if (_clazz != null) { + // SCHEMA-FULL? + final OProperty prop = _clazz.getProperty(iFieldName); + if (prop != null) { + entry.property = prop; + fieldType = prop.getType(); + if (fieldType != OType.ANY) + entry.type = fieldType; + } + } + return fieldType; + } + + private boolean addCollectionChangeListener(final ODocumentEntry entry) { + if (!(entry.value instanceof OTrackedMultiValue)) + return false; + if (entry.changeListener == null) { + final OSimpleMultiValueChangeListener listener = new OSimpleMultiValueChangeListener(this, + entry); + ((OTrackedMultiValue) entry.value).addChangeListener(listener); + entry.changeListener = listener; + } + return true; + } + + private void removeAllCollectionChangeListeners() { + if (_fields == null) + return; + + for (final Map.Entry field : _fields.entrySet()) { + removeCollectionChangeListener(field.getValue(), field.getValue().value); + } + } + + private void addAllMultiValueChangeListeners() { + if (_fields == null) + return; + + for (final Map.Entry field : _fields.entrySet()) { + addCollectionChangeListener(field.getValue()); + } + } + + private void removeCollectionChangeListener(ODocumentEntry entry, Object fieldValue) { + if (entry != null && entry.changeListener != null) { + final OMultiValueChangeListener changeListener = entry.changeListener; + entry.changeListener = null; + entry.timeLine = null; + if (!(fieldValue instanceof OTrackedMultiValue)) + return; + + final OTrackedMultiValue multiValue = (OTrackedMultiValue) fieldValue; + multiValue.removeRecordChangeListener(changeListener); + } + } + + protected void checkClass(ODatabaseDocumentInternal database) { + if (_className == null) + fetchClassName(); + + final OSchema immutableSchema = database.getMetadata().getImmutableSchemaSnapshot(); + if (immutableSchema == null) + return; + + if (_immutableClazz == null) { + _immutableSchemaVersion = immutableSchema.getVersion(); + _immutableClazz = (OImmutableClass) immutableSchema.getClass(_className); + } else { + if (_immutableSchemaVersion < immutableSchema.getVersion()) { + _immutableSchemaVersion = immutableSchema.getVersion(); + _immutableClazz = (OImmutableClass) immutableSchema.getClass(_className); + } + } + + } + + @Override + protected void track(OIdentifiable id) { + if (isTrackingChanges() && id.getIdentity().getClusterId() != -2) + super.track(id); + } + + @Override + protected void unTrack(OIdentifiable id) { + if (isTrackingChanges() && id.getIdentity().getClusterId() != -2) + super.unTrack(id); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentComparator.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentComparator.java new file mode 100644 index 00000000000..9937f831829 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentComparator.java @@ -0,0 +1,118 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import java.text.Collator; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OBasicCommandContext; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabase.ATTRIBUTES; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect; + +/** + * Comparator implementation class used by ODocumentSorter class to sort documents following dynamic criteria. + * + * @author Luca Garulli + * + */ +public class ODocumentComparator implements Comparator { + private List> orderCriteria; + private OCommandContext context; + private Collator collator; + + public ODocumentComparator(final List> iOrderCriteria, OCommandContext iContext) { + this.orderCriteria = iOrderCriteria; + this.context = iContext; + ODatabaseDocumentInternal internal = ODatabaseRecordThreadLocal.INSTANCE.get(); + collator = Collator.getInstance(new Locale(internal.get(ATTRIBUTES.LOCALECOUNTRY) + "_" + + internal.get(ATTRIBUTES.LOCALELANGUAGE))); + } + + @SuppressWarnings("unchecked") + public int compare(final OIdentifiable iDoc1, final OIdentifiable iDoc2) { + if (iDoc1 != null && iDoc1.equals(iDoc2)) + return 0; + + Object fieldValue1; + Object fieldValue2; + + int partialResult = 0; + + for (OPair field : orderCriteria) { + final String fieldName = field.getKey(); + final String ordering = field.getValue(); + + fieldValue1 = ((ODocument) iDoc1.getRecord()).field(fieldName); + fieldValue2 = ((ODocument) iDoc2.getRecord()).field(fieldName); + + if (fieldValue1 == null && fieldValue2 == null) { + continue; + } + + if (fieldValue1 == null) + return factor(-1, ordering); + + if (fieldValue2 == null) + return factor(1, ordering); + + if (!(fieldValue1 instanceof Comparable)) { + context.incrementVariable(OBasicCommandContext.INVALID_COMPARE_COUNT); + partialResult = ("" + fieldValue1).compareTo("" + fieldValue2); + } else { + try { + if (collator != null && fieldValue1 instanceof String && fieldValue2 instanceof String) + partialResult = collator.compare(fieldValue1, fieldValue2); + else + partialResult = ((Comparable) fieldValue1).compareTo(fieldValue2); + } catch (Exception x) { + context.incrementVariable(OBasicCommandContext.INVALID_COMPARE_COUNT); + partialResult = collator.compare("" + fieldValue1, "" + fieldValue2); + } + } + partialResult = factor(partialResult, ordering); + + if (partialResult != 0) + break; + + // CONTINUE WITH THE NEXT FIELD + } + + return partialResult; + } + + private int factor(final int partialResult, final String iOrdering) { + if (iOrdering.equals(OCommandExecutorSQLSelect.KEYWORD_DESC)) + // INVERT THE ORDERING + return partialResult * -1; + + return partialResult; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentEntry.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentEntry.java new file mode 100644 index 00000000000..6ceecdaed6a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentEntry.java @@ -0,0 +1,83 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Document entry. Used by ODocument. + * + * @author Emanuele Tagliaferri + * @since 2.1 + */ +public class ODocumentEntry { + + public Object value; + public Object original; + public OType type; + public OProperty property; + public OSimpleMultiValueChangeListener changeListener; + public OMultiValueChangeTimeLine timeLine; + public boolean changed = false; + public boolean exist = true; + public boolean created = false; + + public ODocumentEntry() { + + } + + public boolean isChanged() { + return changed; + } + + public void setChanged(final boolean changed) { + this.changed = changed; + } + + public boolean exist() { + return exist; + } + + public void setExist(final boolean exist) { + this.exist = exist; + } + + public boolean isCreated() { + return created; + } + + public void setCreated(final boolean created) { + this.created = created; + } + + protected ODocumentEntry clone() { + final ODocumentEntry entry = new ODocumentEntry(); + entry.type = type; + entry.property = property; + entry.value = value; + entry.changed = changed; + entry.created = created; + entry.exist = exist; + return entry; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentHelper.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentHelper.java new file mode 100755 index 00000000000..a61a6d406ee --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentHelper.java @@ -0,0 +1,1588 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ORecordElement.STATUS; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerStringAbstract; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.method.OSQLMethod; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.lang.reflect.Array; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; + +/** + * Helper class to manage documents. + * + * @author Luca Garulli + */ +public class ODocumentHelper { + public static final String ATTRIBUTE_THIS = "@this"; + public static final String ATTRIBUTE_RID = "@rid"; + public static final String ATTRIBUTE_RID_ID = "@rid_id"; + public static final String ATTRIBUTE_RID_POS = "@rid_pos"; + public static final String ATTRIBUTE_VERSION = "@version"; + public static final String ATTRIBUTE_CLASS = "@class"; + public static final String ATTRIBUTE_TYPE = "@type"; + public static final String ATTRIBUTE_SIZE = "@size"; + public static final String ATTRIBUTE_FIELDS = "@fields"; + public static final String ATTRIBUTE_RAW = "@raw"; + + public static interface ODbRelatedCall { + T call(ODatabaseDocumentInternal database); + } + + public static interface RIDMapper { + ORID map(ORID rid); + } + + public static void sort(List ioResultSet, List> iOrderCriteria, + OCommandContext context) { + if (ioResultSet != null) + Collections.sort(ioResultSet, new ODocumentComparator(iOrderCriteria, context)); + } + + @SuppressWarnings("unchecked") + public static RET convertField(final ODocument iDocument, final String iFieldName, OType type, Class iFieldType, + Object iValue) { + if (iFieldType == null && type != null) + iFieldType = type.getDefaultJavaType(); + + if (iFieldType == null) + return (RET) iValue; + + if (ORID.class.isAssignableFrom(iFieldType)) { + if (iValue instanceof ORID) { + return (RET) iValue; + } else if (iValue instanceof String) { + return (RET) new ORecordId((String) iValue); + } else if (iValue instanceof ORecord) { + return (RET) ((ORecord) iValue).getIdentity(); + } + } else if (OIdentifiable.class.isAssignableFrom(iFieldType)) { + if (iValue instanceof ORID || iValue instanceof ORecord) { + return (RET) iValue; + } else if (iValue instanceof String) { + return (RET) new ORecordId((String) iValue); + } + } else if (Set.class.isAssignableFrom(iFieldType)) { + if (!(iValue instanceof Set)) { + // CONVERT IT TO SET + final Collection newValue; + + if (type.isLink()) + newValue = new ORecordLazySet(iDocument); + else + newValue = new OTrackedSet(iDocument); + + if (iValue instanceof Collection) { + ((Collection) newValue).addAll((Collection) iValue); + return (RET) newValue; + } else if (iValue instanceof Map) { + ((Collection) newValue).addAll(((Map) iValue).values()); + return (RET) newValue; + } else if (iValue instanceof String) { + final String stringValue = (String) iValue; + + if (stringValue != null && !stringValue.isEmpty()) { + final String[] items = stringValue.split(","); + for (String s : items) { + ((Collection) newValue).add(s); + } + } + return (RET) newValue; + } else if (OMultiValue.isMultiValue(iValue)) { + // GENERIC MULTI VALUE + for (Object s : OMultiValue.getMultiValueIterable(iValue, false)) { + ((Collection) newValue).add(s); + } + return (RET) newValue; + } + } else { + return (RET) iValue; + } + } else if (List.class.isAssignableFrom(iFieldType)) { + if (!(iValue instanceof List)) { + // CONVERT IT TO LIST + final Collection newValue; + + if (type.isLink()) + newValue = new ORecordLazyList(iDocument); + else + newValue = new OTrackedList(iDocument); + + if (iValue instanceof Collection) { + ((Collection) newValue).addAll((Collection) iValue); + return (RET) newValue; + } else if (iValue instanceof Map) { + ((Collection) newValue).addAll(((Map) iValue).values()); + return (RET) newValue; + } else if (iValue instanceof String) { + final String stringValue = (String) iValue; + + if (stringValue != null && !stringValue.isEmpty()) { + final String[] items = stringValue.split(","); + for (String s : items) { + ((Collection) newValue).add(s); + } + } + return (RET) newValue; + } else if (OMultiValue.isMultiValue(iValue)) { + // GENERIC MULTI VALUE + for (Object s : OMultiValue.getMultiValueIterable(iValue)) { + ((Collection) newValue).add(s); + } + return (RET) newValue; + } + } else { + return (RET) iValue; + } + } else if (iValue instanceof Enum) { + // ENUM + if (Number.class.isAssignableFrom(iFieldType)) + iValue = ((Enum) iValue).ordinal(); + else + iValue = iValue.toString(); + if (!(iValue instanceof String) && !iFieldType.isAssignableFrom(iValue.getClass())) + throw new IllegalArgumentException( + "Property '" + iFieldName + "' of type '" + iFieldType + "' cannot accept value of type: " + iValue.getClass()); + } else if (Date.class.isAssignableFrom(iFieldType)) { + if (iValue instanceof String && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) { + final OStorageConfiguration config = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration(); + + DateFormat formatter = config.getDateFormatInstance(); + + if (((String) iValue).length() > config.dateFormat.length()) { + // ASSUMES YOU'RE USING THE DATE-TIME FORMATTE + formatter = config.getDateTimeFormatInstance(); + } + + try { + Date newValue = formatter.parse((String) iValue); + // _fieldValues.put(iFieldName, newValue); + return (RET) newValue; + } catch (ParseException pe) { + final String dateFormat = + ((String) iValue).length() > config.dateFormat.length() ? config.dateTimeFormat : config.dateFormat; + throw OException.wrapException( + new OQueryParsingException("Error on conversion of date '" + iValue + "' using the format: " + dateFormat), pe); + } + } + } + + iValue = OType.convert(iValue, iFieldType); + + return (RET) iValue; + } + + @SuppressWarnings("unchecked") + public static RET getFieldValue(Object value, final String iFieldName) { + return (RET) getFieldValue(value, iFieldName, null); + } + + @SuppressWarnings("unchecked") + public static RET getFieldValue(Object value, final String iFieldName, final OCommandContext iContext) { + if (value == null) + return null; + + final int fieldNameLength = iFieldName.length(); + if (fieldNameLength == 0) + return (RET) value; + + OIdentifiable currentRecord = value instanceof OIdentifiable ? (OIdentifiable) value : null; + + int beginPos = iFieldName.charAt(0) == '.' ? 1 : 0; + int nextSeparatorPos = iFieldName.charAt(0) == '.' ? 1 : 0; + boolean firstInChain = true; + do { + char nextSeparator = ' '; + for (; nextSeparatorPos < fieldNameLength; ++nextSeparatorPos) { + nextSeparator = iFieldName.charAt(nextSeparatorPos); + if (nextSeparator == '.' || nextSeparator == '[') + break; + } + + final String fieldName; + if (nextSeparatorPos < fieldNameLength) + fieldName = iFieldName.substring(beginPos, nextSeparatorPos); + else { + nextSeparator = ' '; + if (beginPos > 0) + fieldName = iFieldName.substring(beginPos); + else + fieldName = iFieldName; + } + + if (nextSeparator == '[') { + if (fieldName != null && fieldName.length() > 0) { + if (currentRecord != null) + value = getIdentifiableValue(currentRecord, fieldName); + else if (value instanceof Map) + value = getMapEntry((Map) value, fieldName); + else if (OMultiValue.isMultiValue(value)) { + final HashSet temp = new LinkedHashSet(); + for (Object o : OMultiValue.getMultiValueIterable(value, false)) { + if (o instanceof OIdentifiable) { + Object r = getFieldValue(o, iFieldName); + if (r != null) + OMultiValue.add(temp, r); + } + } + value = temp; + } + } + + if (value == null) + return null; + else if (value instanceof OIdentifiable) + currentRecord = (OIdentifiable) value; + + // final int end = iFieldName.indexOf(']', nextSeparatorPos); + final int end = findClosingBracketPosition(iFieldName, nextSeparatorPos); + if (end == -1) + throw new IllegalArgumentException("Missed closed ']'"); + + String indexPart = iFieldName.substring(nextSeparatorPos + 1, end); + if (indexPart.length() == 0) + return null; + + nextSeparatorPos = end; + + if (value instanceof OCommandContext) + value = ((OCommandContext) value).getVariables(); + + if (value instanceof OIdentifiable) { + final ORecord record = + currentRecord != null && currentRecord instanceof OIdentifiable ? ((OIdentifiable) currentRecord).getRecord() : null; + + final boolean backtick; + if ((indexPart.startsWith("`") && indexPart.endsWith("`"))) { + indexPart = OIOUtils.getStringContent(indexPart); + backtick = true; + } else + backtick = false; + + final Object index = getIndexPart(iContext, indexPart); + final String indexAsString = index != null ? index.toString() : null; + + final List indexParts = OStringSerializerHelper + .smartSplit(indexAsString, ',', OStringSerializerHelper.DEFAULT_IGNORE_CHARS); + final List indexRanges = OStringSerializerHelper.smartSplit(indexAsString, '-', ' '); + final List indexCondition = OStringSerializerHelper.smartSplit(indexAsString, '=', ' '); + + if (backtick || (indexParts.size() == 1 && indexCondition.size() == 1 && indexRanges.size() == 1)) + // SINGLE VALUE + value = ((ODocument) record).field(indexAsString); + else if (indexParts.size() > 1) { + // MULTI VALUE + final Object[] values = new Object[indexParts.size()]; + for (int i = 0; i < indexParts.size(); ++i) { + values[i] = ((ODocument) record).field(OIOUtils.getStringContent(indexParts.get(i))); + } + value = values; + } else if (indexRanges.size() > 1) { + + // MULTI VALUES RANGE + String from = indexRanges.get(0); + String to = indexRanges.get(1); + + final ODocument doc = (ODocument) record; + + final String[] fieldNames = doc.fieldNames(); + final int rangeFrom = from != null && !from.isEmpty() ? Integer.parseInt(from) : 0; + final int rangeTo = + to != null && !to.isEmpty() ? Math.min(Integer.parseInt(to), fieldNames.length - 1) : fieldNames.length - 1; + + final Object[] values = new Object[rangeTo - rangeFrom + 1]; + + for (int i = rangeFrom; i <= rangeTo; ++i) + values[i - rangeFrom] = doc.field(fieldNames[i]); + + value = values; + + } else if (!indexCondition.isEmpty()) { + // CONDITION + final String conditionFieldName = indexCondition.get(0); + Object conditionFieldValue = ORecordSerializerStringAbstract.getTypeValue(indexCondition.get(1)); + + if (conditionFieldValue instanceof String) + conditionFieldValue = OIOUtils.getStringContent(conditionFieldValue); + + final Object fieldValue = getFieldValue(currentRecord, conditionFieldName); + + if (conditionFieldValue != null && fieldValue != null) + conditionFieldValue = OType.convert(conditionFieldValue, fieldValue.getClass()); + + if (fieldValue == null && !conditionFieldValue.equals("null") || fieldValue != null && !fieldValue + .equals(conditionFieldValue)) + value = null; + } + } else if (value instanceof Map) { + final boolean backtick; + if ((indexPart.startsWith("`") && indexPart.endsWith("`"))) { + indexPart = OIOUtils.getStringContent(indexPart); + backtick = true; + } else + backtick = false; + + final Object index = getIndexPart(iContext, indexPart); + final String indexAsString = index != null ? index.toString() : null; + + final List indexParts = OStringSerializerHelper + .smartSplit(indexAsString, ',', OStringSerializerHelper.DEFAULT_IGNORE_CHARS); + final List indexRanges = OStringSerializerHelper.smartSplit(indexAsString, '-', ' '); + if (!allIntegers(indexRanges)) { + indexRanges.clear(); + indexRanges.add(indexAsString); + } + final List indexCondition = OStringSerializerHelper.smartSplit(indexAsString, '=', ' '); + + final Map map = (Map) value; + if (backtick || (indexParts.size() == 1 && indexCondition.size() == 1 && indexRanges.size() == 1)) + // SINGLE VALUE + value = map.get(index); + else if (indexParts.size() > 1) { + // MULTI VALUE + final Object[] values = new Object[indexParts.size()]; + for (int i = 0; i < indexParts.size(); ++i) { + values[i] = map.get(OIOUtils.getStringContent(indexParts.get(i))); + } + value = values; + } else if (indexRanges.size() > 1) { + + // MULTI VALUES RANGE + String from = indexRanges.get(0); + String to = indexRanges.get(1); + + final List fieldNames = new ArrayList(map.keySet()); + final int rangeFrom = from != null && !from.isEmpty() ? Integer.parseInt(from) : 0; + final int rangeTo = + to != null && !to.isEmpty() ? Math.min(Integer.parseInt(to), fieldNames.size() - 1) : fieldNames.size() - 1; + + final Object[] values = new Object[rangeTo - rangeFrom + 1]; + + for (int i = rangeFrom; i <= rangeTo; ++i) + values[i - rangeFrom] = map.get(fieldNames.get(i)); + + value = values; + + } else if (!indexCondition.isEmpty()) { + // CONDITION + final String conditionFieldName = indexCondition.get(0); + Object conditionFieldValue = ORecordSerializerStringAbstract.getTypeValue(indexCondition.get(1)); + + if (conditionFieldValue instanceof String) + conditionFieldValue = OIOUtils.getStringContent(conditionFieldValue); + + final Object fieldValue = map.get(conditionFieldName); + + if (conditionFieldValue != null && fieldValue != null) + conditionFieldValue = OType.convert(conditionFieldValue, fieldValue.getClass()); + + if (fieldValue == null && !conditionFieldValue.equals("null") || fieldValue != null && !fieldValue + .equals(conditionFieldValue)) + value = null; + } + + } else if (OMultiValue.isMultiValue(value)) { + // MULTI VALUE + final Object index = getIndexPart(iContext, indexPart); + final String indexAsString = index != null ? index.toString() : null; + + final List indexParts = OStringSerializerHelper.smartSplit(indexAsString, ','); + final List indexRanges = OStringSerializerHelper.smartSplit(indexAsString, '-'); + final List indexCondition = OStringSerializerHelper.smartSplit(indexAsString, '=', ' '); + + if (isFieldName(indexAsString)) { + // SINGLE VALUE + if (value instanceof Map) + value = getMapEntry((Map) value, index); + else if (Character.isDigit(indexAsString.charAt(0))) + value = OMultiValue.getValue(value, Integer.parseInt(indexAsString)); + else + // FILTER BY FIELD + value = getFieldValue(value, indexAsString, iContext); + + } else if (isListOfNumbers(indexParts)) { + + // MULTI VALUES + final Object[] values = new Object[indexParts.size()]; + for (int i = 0; i < indexParts.size(); ++i) + values[i] = OMultiValue.getValue(value, Integer.parseInt(indexParts.get(i))); + if (indexParts.size() > 1) { + value = values; + } else { + value = values[0]; + } + + } else if (isListOfNumbers(indexRanges)) { + + // MULTI VALUES RANGE + String from = indexRanges.get(0); + String to = indexRanges.get(1); + + final int rangeFrom = from != null && !from.isEmpty() ? Integer.parseInt(from) : 0; + final int rangeTo = to != null && !to.isEmpty() ? + Math.min(Integer.parseInt(to), OMultiValue.getSize(value) - 1) : + OMultiValue.getSize(value) - 1; + + int arraySize = rangeTo - rangeFrom + 1; + if (arraySize < 0) { + arraySize = 0; + } + final Object[] values = new Object[arraySize]; + for (int i = rangeFrom; i <= rangeTo; ++i) + values[i - rangeFrom] = OMultiValue.getValue(value, i); + value = values; + + } else { + // CONDITION + OSQLPredicate pred = new OSQLPredicate(indexAsString); + final HashSet values = new LinkedHashSet(); + + for (Object v : OMultiValue.getMultiValueIterable(value)) { + if (v instanceof OIdentifiable) { + Object result = pred.evaluate((OIdentifiable) v, (ODocument) ((OIdentifiable) v).getRecord(), iContext); + if (Boolean.TRUE.equals(result)) { + values.add(v); + } + } else if (v instanceof Map) { + ODocument doc = new ODocument().fromMap((Map) v); + Object result = pred.evaluate(doc, doc, iContext); + if (Boolean.TRUE.equals(result)) { + values.add(v); + } + } + } + + if (values.isEmpty()) + // RETURNS NULL + value = values; + else if (values.size() == 1) + // RETURNS THE SINGLE ODOCUMENT + value = values.iterator().next(); + else + // RETURNS THE FILTERED COLLECTION + value = values; + } + } + } else { + if (fieldName.length() == 0) { + // NO FIELD NAME: THIS IS THE CASE OF NOT USEFUL . AFTER A ] OR . + beginPos = ++nextSeparatorPos; + continue; + } + + if (fieldName.startsWith("$")) + value = iContext.getVariable(fieldName); + else if (fieldName.contains("(")) { + boolean executedMethod = false; + if (!firstInChain && fieldName.endsWith("()")) { + OSQLMethod method = OSQLEngine.getInstance().getMethod(fieldName.substring(0, fieldName.length() - 2)); + if (method != null) { + value = method.execute(value, currentRecord, iContext, value, new Object[] {}); + executedMethod = true; + } + } + if (!executedMethod) { + value = evaluateFunction(value, fieldName, iContext); + } + } else { + final List indexCondition = OStringSerializerHelper.smartSplit(fieldName, '=', ' '); + + if (indexCondition.size() == 2) { + final String conditionFieldName = indexCondition.get(0); + Object conditionFieldValue = ORecordSerializerStringAbstract.getTypeValue(indexCondition.get(1)); + + if (conditionFieldValue instanceof String) + conditionFieldValue = OIOUtils.getStringContent(conditionFieldValue); + + value = filterItem(conditionFieldName, conditionFieldValue, value); + + } else if (currentRecord != null) { + // GET THE LINKED OBJECT IF ANY + value = getIdentifiableValue(currentRecord, fieldName); + if (value != null && value instanceof ORecord && ((ORecord) value).getInternalStatus() == STATUS.NOT_LOADED) + // RELOAD IT + ((ORecord) value).reload(); + } else if (value instanceof Map) + value = getMapEntry((Map) value, fieldName); + else if (OMultiValue.isMultiValue(value)) { + final Set values = new LinkedHashSet(); + for (Object v : OMultiValue.getMultiValueIterable(value, false)) { + final Object item; + + if (v instanceof OIdentifiable) + item = getIdentifiableValue((OIdentifiable) v, fieldName); + else if (v instanceof Map) + item = ((Map) v).get(fieldName); + else + item = null; + + if (item != null) + if (item instanceof Collection) + values.addAll((Collection) item); + else + values.add(item); + } + + if (values.isEmpty()) + value = null; + else + value = values; + } else + return null; + } + } + + if (value instanceof OIdentifiable) + currentRecord = (OIdentifiable) value; + else + currentRecord = null; + + beginPos = ++nextSeparatorPos; + firstInChain = false; + } while (nextSeparatorPos < fieldNameLength && value != null); + + return (RET) value; + } + + private static boolean allIntegers(List indexRanges) { + if (indexRanges == null) { + return true; + } + for (String s : indexRanges) { + try { + Integer.parseInt(s); + } catch (Exception e) { + return false; + } + } + return true; + } + + private static int findClosingBracketPosition(String iFieldName, int nextSeparatorPos) { + Character currentQuote = null; + boolean escaping = false; + int innerBrackets = 0; + char[] chars = iFieldName.toCharArray(); + for (int i = nextSeparatorPos + 1; i < chars.length; i++) { + char next = chars[i]; + if (escaping) { + escaping = false; + } else if (next == '\\') { + escaping = true; + } else if (next == '`' || next == '\'' || next == '"') { + if (currentQuote == null) { + currentQuote = next; + } else if (currentQuote == next) { + currentQuote = null; + } + + } else if (next == '[') { + innerBrackets++; + } else if (next == ']') { + if (innerBrackets == 0) { + return i; + } + innerBrackets--; + } + } + return -1; + } + + private static boolean isFieldName(String indexAsString) { + indexAsString = indexAsString.trim(); + if (indexAsString.startsWith("`") && indexAsString.endsWith("`")) { + // quoted identifier + return !indexAsString.substring(1, indexAsString.length() - 1).contains("`"); + } + boolean firstChar = true; + for (char c : indexAsString.toCharArray()) { + if (isLetter(c) || (isNumber(c) && !firstChar)) { + firstChar = false; + continue; + } + return false; + } + return true; + } + + private static boolean isNumber(char c) { + return c >= '0' && c <= '9'; + } + + private static boolean isLetter(char c) { + if (c == '$' || c == '_' || c == '@') { + return true; + } + if (c >= 'a' && c <= 'z') { + return true; + } + if (c >= 'A' && c <= 'Z') { + return true; + } + + return false; + } + + private static boolean isListOfNumbers(List list) { + for (String s : list) { + try { + Integer.parseInt(s); + } catch (NumberFormatException e) { + return false; + } + } + return true; + } + + protected static Object getIndexPart(final OCommandContext iContext, final String indexPart) { + Object index = indexPart; + + if (indexPart.indexOf(',') == -1 && (indexPart.charAt(0) == '"' || indexPart.charAt(0) == '\'')) + index = OIOUtils.getStringContent(indexPart); + else if (indexPart.charAt(0) == '$') { + final Object ctxValue = iContext.getVariable(indexPart); + if (ctxValue == null) + return null; + index = ctxValue; + } else if (!Character.isDigit(indexPart.charAt(0))) + // GET FROM CURRENT VALUE + index = indexPart; + return index; + } + + @SuppressWarnings("unchecked") + protected static Object filterItem(final String iConditionFieldName, final Object iConditionFieldValue, final Object iValue) { + if (iValue instanceof OIdentifiable) { + final ORecord rec = ((OIdentifiable) iValue).getRecord(); + if (rec instanceof ODocument) { + final ODocument doc = (ODocument) rec; + + Object fieldValue = doc.field(iConditionFieldName); + + if (iConditionFieldValue == null) + return fieldValue == null ? doc : null; + + fieldValue = OType.convert(fieldValue, iConditionFieldValue.getClass()); + if (fieldValue != null && fieldValue.equals(iConditionFieldValue)) + return doc; + } + } else if (iValue instanceof Map) { + final Map map = (Map) iValue; + Object fieldValue = getMapEntry(map, iConditionFieldName); + + fieldValue = OType.convert(fieldValue, iConditionFieldValue.getClass()); + if (fieldValue != null && fieldValue.equals(iConditionFieldValue)) + return map; + } + return null; + } + + /** + * Retrieves the value crossing the map with the dotted notation + * + * @param iKey Field(s) to retrieve. If are multiple fields, then the dot must be used as separator + * @param iMap + * + * @return + */ + @SuppressWarnings("unchecked") + public static Object getMapEntry(final Map iMap, final Object iKey) { + if (iMap == null || iKey == null) + return null; + + if (iKey instanceof String) { + String iName = (String) iKey; + int pos = iName.indexOf('.'); + if (pos > -1) + iName = iName.substring(0, pos); + + final Object value = iMap.get(iName); + if (value == null) + return null; + + if (pos > -1) { + final String restFieldName = iName.substring(pos + 1); + if (value instanceof ODocument) + return getFieldValue(value, restFieldName); + else if (value instanceof Map) + return getMapEntry((Map) value, restFieldName); + } + + return value; + } else + return iMap.get(iKey); + } + + public static Object getIdentifiableValue(final OIdentifiable iCurrent, final String iFieldName) { + if (iFieldName == null || iFieldName.length() == 0) + return null; + + final char begin = iFieldName.charAt(0); + if (begin == '@') { + // RETURN AN ATTRIBUTE + if (iFieldName.equalsIgnoreCase(ATTRIBUTE_THIS)) + return iCurrent.getRecord(); + else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_RID)) + return iCurrent.getIdentity(); + else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_RID_ID)) + return iCurrent.getIdentity().getClusterId(); + else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_RID_POS)) + return iCurrent.getIdentity().getClusterPosition(); + else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_VERSION)) { + final ORecord rec = iCurrent.getRecord(); + if (rec != null) + return rec.getVersion(); + return null; + } else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_CLASS)) { + final ODocument rec = iCurrent.getRecord(); + if (rec != null) + return rec.getClassName(); + return null; + } else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_TYPE)) { + final ORecord rec = iCurrent.getRecord(); + if (rec != null) + return Orient.instance().getRecordFactoryManager().getRecordTypeName(ORecordInternal.getRecordType(rec)); + return null; + } else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_SIZE)) { + final ORecord rec = iCurrent.getRecord(); + if (rec != null) { + final byte[] stream = rec.toStream(); + return stream != null ? stream.length : 0; + } + return null; + } else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_FIELDS)) { + final ODocument rec = iCurrent.getRecord(); + if (rec != null) + return rec.fieldNames(); + return null; + } else if (iFieldName.equalsIgnoreCase(ATTRIBUTE_RAW)) { + final ODocument rec = iCurrent.getRecord(); + if (rec != null) + return new String(rec.toStream()); + return null; + } + } + + if (iCurrent == null) + return null; + + final ODocument doc = ((ODocument) iCurrent.getRecord()); + if (doc == null) {// broken link + return null; + } + doc.checkForFields(iFieldName); + ODocumentEntry entry = doc._fields.get(iFieldName); + return entry != null ? entry.value : null; + } + + public static Object evaluateFunction(final Object currentValue, final String iFunction, final OCommandContext iContext) { + if (currentValue == null) + return null; + + Object result = null; + + final String function = iFunction.toUpperCase(Locale.ENGLISH); + + if (function.startsWith("SIZE(")) + result = currentValue instanceof ORecord ? 1 : OMultiValue.getSize(currentValue); + else if (function.startsWith("LENGTH(")) + result = currentValue.toString().length(); + else if (function.startsWith("TOUPPERCASE(")) + result = currentValue.toString().toUpperCase(Locale.ENGLISH); + else if (function.startsWith("TOLOWERCASE(")) + result = currentValue.toString().toLowerCase(Locale.ENGLISH); + else if (function.startsWith("TRIM(")) + result = currentValue.toString().trim(); + else if (function.startsWith("TOJSON(")) + result = currentValue instanceof ODocument ? ((ODocument) currentValue).toJSON() : null; + else if (function.startsWith("KEYS(")) + result = currentValue instanceof Map ? ((Map) currentValue).keySet() : null; + else if (function.startsWith("VALUES(")) + result = currentValue instanceof Map ? ((Map) currentValue).values() : null; + else if (function.startsWith("ASSTRING(")) + result = currentValue.toString(); + else if (function.startsWith("ASINTEGER(")) + result = new Integer(currentValue.toString()); + else if (function.startsWith("ASFLOAT(")) + result = new Float(currentValue.toString()); + else if (function.startsWith("ASBOOLEAN(")) { + if (currentValue instanceof String) + result = new Boolean((String) currentValue); + else if (currentValue instanceof Number) { + final int bValue = ((Number) currentValue).intValue(); + if (bValue == 0) + result = Boolean.FALSE; + else if (bValue == 1) + result = Boolean.TRUE; + } + } else if (function.startsWith("ASDATE(")) + if (currentValue instanceof Date) + result = currentValue; + else if (currentValue instanceof Number) + result = new Date(((Number) currentValue).longValue()); + else + try { + result = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateFormatInstance() + .parse(currentValue.toString()); + } catch (ParseException e) { + } + else if (function.startsWith("ASDATETIME(")) + if (currentValue instanceof Date) + result = currentValue; + else if (currentValue instanceof Number) + result = new Date(((Number) currentValue).longValue()); + else + try { + result = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateTimeFormatInstance() + .parse(currentValue.toString()); + } catch (ParseException e) { + } + else { + // EXTRACT ARGUMENTS + final List args = OStringSerializerHelper.getParameters(iFunction.substring(iFunction.indexOf('('))); + + final ORecord currentRecord = iContext != null ? (ORecord) iContext.getVariable("$current") : null; + for (int i = 0; i < args.size(); ++i) { + final String arg = args.get(i); + final Object o = OSQLHelper.getValue(arg, currentRecord, iContext); + if (o != null) + args.set(i, o.toString()); + } + + if (function.startsWith("CHARAT(")) + result = currentValue.toString().charAt(Integer.parseInt(args.get(0))); + else if (function.startsWith("INDEXOF(")) + if (args.size() == 1) + result = currentValue.toString().indexOf(OIOUtils.getStringContent(args.get(0))); + else + result = currentValue.toString().indexOf(OIOUtils.getStringContent(args.get(0)), Integer.parseInt(args.get(1))); + else if (function.startsWith("SUBSTRING(")) + if (args.size() == 1) + result = currentValue.toString().substring(Integer.parseInt(args.get(0))); + else + result = currentValue.toString().substring(Integer.parseInt(args.get(0)), Integer.parseInt(args.get(1))); + else if (function.startsWith("APPEND(")) + result = currentValue.toString() + OIOUtils.getStringContent(args.get(0)); + else if (function.startsWith("PREFIX(")) + result = OIOUtils.getStringContent(args.get(0)) + currentValue.toString(); + else if (function.startsWith("FORMAT(")) + if (currentValue instanceof Date) { + SimpleDateFormat formatter = new SimpleDateFormat(OIOUtils.getStringContent(args.get(0))); + formatter.setTimeZone(ODateHelper.getDatabaseTimeZone()); + result = formatter.format(currentValue); + } else + result = String.format(OIOUtils.getStringContent(args.get(0)), currentValue.toString()); + else if (function.startsWith("LEFT(")) { + final int len = Integer.parseInt(args.get(0)); + final String stringValue = currentValue.toString(); + result = stringValue.substring(0, len <= stringValue.length() ? len : stringValue.length()); + } else if (function.startsWith("RIGHT(")) { + final int offset = Integer.parseInt(args.get(0)); + final String stringValue = currentValue.toString(); + result = stringValue.substring(offset < stringValue.length() ? stringValue.length() - offset : 0); + } else { + final OSQLFunctionRuntime f = OSQLHelper.getFunction(null, iFunction); + if (f != null) + result = f.execute(currentRecord, currentRecord, null, iContext); + } + } + + return result; + } + + @SuppressWarnings("unchecked") + public static Object cloneValue(ODocument iCloned, final Object fieldValue) { + + if (fieldValue != null) { + if (fieldValue instanceof ODocument && !((ODocument) fieldValue).getIdentity().isValid()) { + // EMBEDDED DOCUMENT + return ((ODocument) fieldValue).copy(); + + } else if (fieldValue instanceof ORidBag) { + ORidBag newBag = ((ORidBag) fieldValue).copy(); + newBag.setOwner(iCloned); + return newBag; + + } else if (fieldValue instanceof ORecordLazyList) { + return ((ORecordLazyList) fieldValue).copy(iCloned); + + } else if (fieldValue instanceof ORecordTrackedList) { + final ORecordTrackedList newList = new ORecordTrackedList(iCloned); + newList.addAll((ORecordTrackedList) fieldValue); + return newList; + + } else if (fieldValue instanceof OTrackedList) { + final OTrackedList newList = new OTrackedList(iCloned); + newList.addAll((OTrackedList) fieldValue); + return newList; + + } else if (fieldValue instanceof List) { + return new ArrayList((List) fieldValue); + + // SETS + } else if (fieldValue instanceof ORecordLazySet) { + final ORecordLazySet newList = new ORecordLazySet(iCloned); + newList.addAll((ORecordLazySet) fieldValue); + return newList; + + } else if (fieldValue instanceof ORecordTrackedSet) { + final ORecordTrackedSet newList = new ORecordTrackedSet(iCloned); + newList.addAll((ORecordTrackedSet) fieldValue); + return newList; + + } else if (fieldValue instanceof OTrackedSet) { + final OTrackedSet newList = new OTrackedSet(iCloned); + newList.addAll((OTrackedSet) fieldValue); + return newList; + + } else if (fieldValue instanceof Set) { + return new HashSet((Set) fieldValue); + // MAPS + } else if (fieldValue instanceof ORecordLazyMap) { + final ORecordLazyMap newMap = new ORecordLazyMap(iCloned, ((ORecordLazyMap) fieldValue).getRecordType()); + newMap.putAll((ORecordLazyMap) fieldValue); + return newMap; + + } else if (fieldValue instanceof OTrackedMap) { + final OTrackedMap newMap = new OTrackedMap(iCloned); + newMap.putAll((OTrackedMap) fieldValue); + return newMap; + + } else if (fieldValue instanceof Map) { + return new LinkedHashMap((Map) fieldValue); + } else + return fieldValue; + } + // else if (iCloned.getImmutableSchemaClass() != null) { + // final OProperty prop = iCloned.getImmutableSchemaClass().getProperty(iEntry.getKey()); + // if (prop != null && prop.isMandatory()) + // return fieldValue; + // } + return null; + } + + public static boolean hasSameContentItem(final Object iCurrent, ODatabaseDocumentInternal iMyDb, final Object iOther, + final ODatabaseDocumentInternal iOtherDb, RIDMapper ridMapper) { + if (iCurrent instanceof ODocument) { + final ODocument current = (ODocument) iCurrent; + if (iOther instanceof ORID) { + if (!current.isDirty()) { + ORID id; + if (ridMapper != null) { + ORID mappedId = ridMapper.map(current.getIdentity()); + if (mappedId != null) + id = mappedId; + else + id = current.getIdentity(); + } else + id = current.getIdentity(); + + if (!id.equals(iOther)) { + return false; + } + } else { + final ODocument otherDoc = iOtherDb.load((ORID) iOther); + if (!ODocumentHelper.hasSameContentOf(current, iMyDb, otherDoc, iOtherDb, ridMapper)) + return false; + } + } else if (!ODocumentHelper.hasSameContentOf(current, iMyDb, (ODocument) iOther, iOtherDb, ridMapper)) + return false; + } else if (!compareScalarValues(iCurrent, iMyDb, iOther, iOtherDb, ridMapper)) + return false; + return true; + } + + /** + * Makes a deep comparison field by field to check if the passed ODocument instance is identical as identity and content to the + * current one. Instead equals() just checks if the RID are the same. + * + * @param iOther ODocument instance + * + * @return true if the two document are identical, otherwise false + * + * @see #equals(Object) + */ + @SuppressWarnings("unchecked") + public static boolean hasSameContentOf(final ODocument iCurrent, final ODatabaseDocumentInternal iMyDb, final ODocument iOther, + final ODatabaseDocumentInternal iOtherDb, RIDMapper ridMapper) { + return hasSameContentOf(iCurrent, iMyDb, iOther, iOtherDb, ridMapper, true); + } + + /** + * Makes a deep comparison field by field to check if the passed ODocument instance is identical in the content to the current + * one. Instead equals() just checks if the RID are the same. + * + * @param iOther ODocument instance + * + * @return true if the two document are identical, otherwise false + * + * @see #equals(Object) + */ + @SuppressWarnings("unchecked") + public static boolean hasSameContentOf(final ODocument iCurrent, final ODatabaseDocumentInternal iMyDb, final ODocument iOther, + final ODatabaseDocumentInternal iOtherDb, RIDMapper ridMapper, final boolean iCheckAlsoIdentity) { + if (iOther == null) + return false; + + if (iCheckAlsoIdentity && iCurrent.getIdentity().isValid() && !iCurrent.getIdentity().equals(iOther.getIdentity())) + return false; + + if (iMyDb != null) + makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + if (iCurrent.getInternalStatus() == STATUS.NOT_LOADED) + iCurrent.reload(); + return null; + } + }); + + if (iOtherDb != null) + makeDbCall(iOtherDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + if (iOther.getInternalStatus() == STATUS.NOT_LOADED) + iOther.reload(); + return null; + } + }); + + if (iMyDb != null) + makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + iCurrent.checkForFields(); + return null; + } + }); + else + iCurrent.checkForFields(); + + if (iOtherDb != null) + makeDbCall(iOtherDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + iOther.checkForFields(); + return null; + } + }); + else + iOther.checkForFields(); + + if (iCurrent.fields() != iOther.fields()) + return false; + + // CHECK FIELD-BY-FIELD + Object myFieldValue; + Object otherFieldValue; + for (Entry f : iCurrent) { + myFieldValue = f.getValue(); + otherFieldValue = iOther._fields.get(f.getKey()).value; + + if (myFieldValue == otherFieldValue) + continue; + + // CHECK FOR NULLS + if (myFieldValue == null) { + if (otherFieldValue != null) + return false; + } else if (otherFieldValue == null) + return false; + + if (myFieldValue != null) + if (myFieldValue instanceof Set && otherFieldValue instanceof Set) { + if (!compareSets(iMyDb, (Set) myFieldValue, iOtherDb, (Set) otherFieldValue, ridMapper)) + return false; + } else if (myFieldValue instanceof Collection && otherFieldValue instanceof Collection) { + if (!compareCollections(iMyDb, (Collection) myFieldValue, iOtherDb, (Collection) otherFieldValue, ridMapper)) + return false; + } else if (myFieldValue instanceof ORidBag && otherFieldValue instanceof ORidBag) { + if (!compareBags(iMyDb, (ORidBag) myFieldValue, iOtherDb, (ORidBag) otherFieldValue, ridMapper)) + return false; + } else if (myFieldValue instanceof Map && otherFieldValue instanceof Map) { + if (!compareMaps(iMyDb, (Map) myFieldValue, iOtherDb, (Map) otherFieldValue, ridMapper)) + return false; + } else if (myFieldValue instanceof ODocument && otherFieldValue instanceof ODocument) { + if (!hasSameContentOf((ODocument) myFieldValue, iMyDb, (ODocument) otherFieldValue, iOtherDb, ridMapper)) + return false; + } else { + if (!compareScalarValues(myFieldValue, iMyDb, otherFieldValue, iOtherDb, ridMapper)) + return false; + } + } + + return true; + } + + public static boolean compareMaps(ODatabaseDocumentInternal iMyDb, Map myFieldValue, + ODatabaseDocumentInternal iOtherDb, Map otherFieldValue, RIDMapper ridMapper) { + // CHECK IF THE ORDER IS RESPECTED + final Map myMap = myFieldValue; + final Map otherMap = otherFieldValue; + + if (myMap.size() != otherMap.size()) + return false; + + boolean oldMyAutoConvert = false; + boolean oldOtherAutoConvert = false; + + if (myMap instanceof ORecordLazyMultiValue) { + oldMyAutoConvert = ((ORecordLazyMultiValue) myMap).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) myMap).setAutoConvertToRecord(false); + } + + if (otherMap instanceof ORecordLazyMultiValue) { + oldOtherAutoConvert = ((ORecordLazyMultiValue) otherMap).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) otherMap).setAutoConvertToRecord(false); + } + + try { + final Iterator> myEntryIterator = makeDbCall(iMyDb, + new ODbRelatedCall>>() { + public Iterator> call(ODatabaseDocumentInternal database) { + return myMap.entrySet().iterator(); + } + }); + + while (makeDbCall(iMyDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return myEntryIterator.hasNext(); + } + })) { + final Entry myEntry = makeDbCall(iMyDb, new ODbRelatedCall>() { + public Entry call(ODatabaseDocumentInternal database) { + return myEntryIterator.next(); + } + }); + final Object myKey = makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return myEntry.getKey(); + } + }); + + if (makeDbCall(iOtherDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return !otherMap.containsKey(myKey); + } + })) + return false; + + if (myEntry.getValue() instanceof ODocument) { + if (!hasSameContentOf(makeDbCall(iMyDb, new ODbRelatedCall() { + public ODocument call(ODatabaseDocumentInternal database) { + return (ODocument) myEntry.getValue(); + } + }), iMyDb, makeDbCall(iOtherDb, new ODbRelatedCall() { + public ODocument call(ODatabaseDocumentInternal database) { + return (ODocument) otherMap.get(myEntry.getKey()); + } + }), iOtherDb, ridMapper)) + return false; + } else { + final Object myValue = makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return myEntry.getValue(); + } + }); + + final Object otherValue = makeDbCall(iOtherDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return otherMap.get(myEntry.getKey()); + } + }); + + if (!compareScalarValues(myValue, iMyDb, otherValue, iOtherDb, ridMapper)) + return false; + } + } + return true; + } finally { + if (myMap instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) myMap).setAutoConvertToRecord(oldMyAutoConvert); + + if (otherMap instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) otherMap).setAutoConvertToRecord(oldOtherAutoConvert); + } + } + + public static boolean compareCollections(ODatabaseDocumentInternal iMyDb, Collection myFieldValue, + ODatabaseDocumentInternal iOtherDb, Collection otherFieldValue, RIDMapper ridMapper) { + final Collection myCollection = myFieldValue; + final Collection otherCollection = otherFieldValue; + + if (myCollection.size() != otherCollection.size()) + return false; + + boolean oldMyAutoConvert = false; + boolean oldOtherAutoConvert = false; + + if (myCollection instanceof ORecordLazyMultiValue) { + oldMyAutoConvert = ((ORecordLazyMultiValue) myCollection).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) myCollection).setAutoConvertToRecord(false); + } + + if (otherCollection instanceof ORecordLazyMultiValue) { + oldOtherAutoConvert = ((ORecordLazyMultiValue) otherCollection).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) otherCollection).setAutoConvertToRecord(false); + } + + try { + final Iterator myIterator = makeDbCall(iMyDb, new ODbRelatedCall>() { + public Iterator call(ODatabaseDocumentInternal database) { + return myCollection.iterator(); + } + }); + + final Iterator otherIterator = makeDbCall(iOtherDb, new ODbRelatedCall>() { + public Iterator call(ODatabaseDocumentInternal database) { + return otherCollection.iterator(); + } + }); + + while (makeDbCall(iMyDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return myIterator.hasNext(); + } + })) { + final Object myNextVal = makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return myIterator.next(); + } + }); + + final Object otherNextVal = makeDbCall(iOtherDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return otherIterator.next(); + } + }); + + if (!hasSameContentItem(myNextVal, iMyDb, otherNextVal, iOtherDb, ridMapper)) + return false; + } + return true; + } finally { + if (myCollection instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) myCollection).setAutoConvertToRecord(oldMyAutoConvert); + + if (otherCollection instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) otherCollection).setAutoConvertToRecord(oldOtherAutoConvert); + } + } + + public static boolean compareSets(ODatabaseDocumentInternal iMyDb, Set myFieldValue, ODatabaseDocumentInternal iOtherDb, + Set otherFieldValue, RIDMapper ridMapper) { + final Set mySet = myFieldValue; + final Set otherSet = otherFieldValue; + + final int mySize = makeDbCall(iMyDb, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return mySet.size(); + } + }); + + final int otherSize = makeDbCall(iOtherDb, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return otherSet.size(); + } + }); + + if (mySize != otherSize) + return false; + + boolean oldMyAutoConvert = false; + boolean oldOtherAutoConvert = false; + + if (mySet instanceof ORecordLazyMultiValue) { + oldMyAutoConvert = ((ORecordLazyMultiValue) mySet).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) mySet).setAutoConvertToRecord(false); + } + + if (otherSet instanceof ORecordLazyMultiValue) { + oldOtherAutoConvert = ((ORecordLazyMultiValue) otherSet).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) otherSet).setAutoConvertToRecord(false); + } + + try { + final Iterator myIterator = makeDbCall(iMyDb, new ODbRelatedCall>() { + public Iterator call(ODatabaseDocumentInternal database) { + return mySet.iterator(); + } + }); + + while (makeDbCall(iMyDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return myIterator.hasNext(); + } + })) { + + final Iterator otherIterator = makeDbCall(iOtherDb, new ODbRelatedCall>() { + public Iterator call(ODatabaseDocumentInternal database) { + return otherSet.iterator(); + } + }); + + final Object myNextVal = makeDbCall(iMyDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return myIterator.next(); + } + }); + + boolean found = false; + while (!found && makeDbCall(iOtherDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return otherIterator.hasNext(); + } + })) { + final Object otherNextVal = makeDbCall(iOtherDb, new ODbRelatedCall() { + public Object call(ODatabaseDocumentInternal database) { + return otherIterator.next(); + } + }); + + found = hasSameContentItem(myNextVal, iMyDb, otherNextVal, iOtherDb, ridMapper); + } + + if (!found) + return false; + } + return true; + } finally { + if (mySet instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) mySet).setAutoConvertToRecord(oldMyAutoConvert); + + if (otherSet instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) otherSet).setAutoConvertToRecord(oldOtherAutoConvert); + } + } + + public static boolean compareBags(ODatabaseDocumentInternal iMyDb, ORidBag myFieldValue, ODatabaseDocumentInternal iOtherDb, + ORidBag otherFieldValue, RIDMapper ridMapper) { + final ORidBag myBag = myFieldValue; + final ORidBag otherBag = otherFieldValue; + + final int mySize = makeDbCall(iMyDb, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return myBag.size(); + } + }); + + final int otherSize = makeDbCall(iOtherDb, new ODbRelatedCall() { + public Integer call(ODatabaseDocumentInternal database) { + return otherBag.size(); + } + }); + + if (mySize != otherSize) + return false; + + boolean oldMyAutoConvert; + boolean oldOtherAutoConvert; + + oldMyAutoConvert = myBag.isAutoConvertToRecord(); + myBag.setAutoConvertToRecord(false); + + oldOtherAutoConvert = otherBag.isAutoConvertToRecord(); + otherBag.setAutoConvertToRecord(false); + + final ORidBag otherBagCopy = makeDbCall(iOtherDb, new ODbRelatedCall() { + @Override + public ORidBag call(ODatabaseDocumentInternal database) { + final ORidBag otherRidBag = new ORidBag(); + otherRidBag.setAutoConvertToRecord(false); + + for (OIdentifiable identifiable : otherBag) + otherRidBag.add(identifiable); + + return otherRidBag; + } + }); + + try { + final Iterator myIterator = makeDbCall(iMyDb, new ODbRelatedCall>() { + public Iterator call(ODatabaseDocumentInternal database) { + return myBag.iterator(); + } + }); + + while (makeDbCall(iMyDb, new ODbRelatedCall() { + public Boolean call(ODatabaseDocumentInternal database) { + return myIterator.hasNext(); + } + })) { + final OIdentifiable myIdentifiable = makeDbCall(iMyDb, new ODbRelatedCall() { + @Override + public OIdentifiable call(ODatabaseDocumentInternal database) { + return myIterator.next(); + } + }); + + final ORID otherRid; + if (ridMapper != null) { + ORID convertedRid = ridMapper.map(myIdentifiable.getIdentity()); + if (convertedRid != null) + otherRid = convertedRid; + else + otherRid = myIdentifiable.getIdentity(); + } else + otherRid = myIdentifiable.getIdentity(); + + makeDbCall(iOtherDb, new ODbRelatedCall() { + @Override + public Object call(ODatabaseDocumentInternal database) { + otherBagCopy.remove(otherRid); + return null; + } + }); + + } + + return makeDbCall(iOtherDb, new ODbRelatedCall() { + @Override + public Boolean call(ODatabaseDocumentInternal database) { + return otherBagCopy.isEmpty(); + } + }); + } finally { + myBag.setAutoConvertToRecord(oldMyAutoConvert); + otherBag.setAutoConvertToRecord(oldOtherAutoConvert); + } + } + + private static boolean compareScalarValues(Object myValue, ODatabaseDocumentInternal iMyDb, Object otherValue, + ODatabaseDocumentInternal iOtherDb, RIDMapper ridMapper) { + if (myValue == null && otherValue != null || myValue != null && otherValue == null) + return false; + + if (myValue == null) + return true; + + if (myValue.getClass().isArray() && !otherValue.getClass().isArray() || !myValue.getClass().isArray() && otherValue.getClass() + .isArray()) + return false; + + if (myValue.getClass().isArray() && otherValue.getClass().isArray()) { + final int myArraySize = Array.getLength(myValue); + final int otherArraySize = Array.getLength(otherValue); + + if (myArraySize != otherArraySize) + return false; + + for (int i = 0; i < myArraySize; i++) { + final Object first = Array.get(myValue, i); + final Object second = Array.get(otherValue, i); + if (first == null && second != null) + return false; + if (first instanceof ODocument && second instanceof ODocument) + return hasSameContentOf((ODocument) first, iMyDb, (ODocument) second, iOtherDb, ridMapper); + + if (first != null && !first.equals(second)) + return false; + } + + return true; + } + + if (myValue instanceof Number && otherValue instanceof Number) { + final Number myNumberValue = (Number) myValue; + final Number otherNumberValue = (Number) otherValue; + + if (isInteger(myNumberValue) && isInteger(otherNumberValue)) + return myNumberValue.longValue() == otherNumberValue.longValue(); + else if (isFloat(myNumberValue) && isFloat(otherNumberValue)) + return myNumberValue.doubleValue() == otherNumberValue.doubleValue(); + } + + if (ridMapper != null && myValue instanceof ORID && otherValue instanceof ORID && ((ORID) myValue).isPersistent()) { + ORID convertedValue = ridMapper.map((ORID) myValue); + if (convertedValue != null) + myValue = convertedValue; + } + + return myValue.equals(otherValue); + } + + private static boolean isInteger(Number value) { + return value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long; + + } + + private static boolean isFloat(Number value) { + return value instanceof Float || value instanceof Double; + } + + public static void deleteCrossRefs(final ORID iRid, final ODocument iContent) { + for (String fieldName : iContent.fieldNames()) { + final Object fieldValue = iContent.field(fieldName); + if (fieldValue != null) { + if (fieldValue.equals(iRid)) { + // REMOVE THE LINK + iContent.field(fieldName, (ORID) null); + iContent.save(); + } else if (fieldValue instanceof ODocument && ((ODocument) fieldValue).isEmbedded()) { + // EMBEDDED DOCUMENT: GO RECURSIVELY + deleteCrossRefs(iRid, (ODocument) fieldValue); + } else if (OMultiValue.isMultiValue(fieldValue)) { + // MULTI-VALUE (COLLECTION, ARRAY OR MAP), CHECK THE CONTENT + for (final Iterator it = OMultiValue.getMultiValueIterator(fieldValue); it.hasNext(); ) { + final Object item = it.next(); + + if (fieldValue.equals(iRid)) { + // DELETE ITEM + it.remove(); + } else if (item instanceof ODocument && ((ODocument) item).isEmbedded()) { + // EMBEDDED DOCUMENT: GO RECURSIVELY + deleteCrossRefs(iRid, (ODocument) item); + } + } + } + } + } + } + + public static T makeDbCall(final ODatabaseDocumentInternal databaseRecord, final ODbRelatedCall function) { + if (databaseRecord != null) + databaseRecord.activateOnCurrentThread(); + return function.call(databaseRecord); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentInternal.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentInternal.java new file mode 100644 index 00000000000..ccdcf20eb73 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentInternal.java @@ -0,0 +1,87 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.metadata.schema.OGlobalProperty; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.Map.Entry; +import java.util.Set; + +public class ODocumentInternal { + + public static void convertAllMultiValuesToTrackedVersions(ODocument document) { + document.convertAllMultiValuesToTrackedVersions(); + } + + public static void addOwner(ODocument oDocument, ORecordElement iOwner) { + oDocument.addOwner(iOwner); + } + + public static void removeOwner(ODocument oDocument, ORecordElement iOwner) { + oDocument.removeOwner(iOwner); + } + + public static void rawField(final ODocument oDocument, final String iFieldName, final Object iFieldValue, + final OType iFieldType) { + oDocument.rawField(iFieldName, iFieldValue, iFieldType); + } + + public static boolean rawContainsField(final ODocument oDocument, final String iFiledName) { + return oDocument.rawContainsField(iFiledName); + } + + public static OImmutableClass getImmutableSchemaClass(final ODocument oDocument) { + if (oDocument == null) { + return null; + } + return oDocument.getImmutableSchemaClass(); + } + + public static OGlobalProperty getGlobalPropertyById(final ODocument oDocument, final int id) { + return oDocument.getGlobalPropertyById(id); + } + + public static void fillClassNameIfNeeded(final ODocument oDocument, String className) { + oDocument.fillClassIfNeed(className); + } + + public static Set> rawEntries(final ODocument document) { + return document.getRawEntries(); + } + + public static void clearTrackData(final ODocument document) { + document.clearTrackData(); + } + + public static void checkClass(ODocument doc, ODatabaseDocumentInternal database) { + doc.checkClass(database); + } + + public static void autoConvertValueToClass(ODatabaseDocumentInternal database, ODocument doc) { + doc.autoConvertFieldsToClass(database); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ONestedMultiValueChangeEvent.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ONestedMultiValueChangeEvent.java new file mode 100644 index 00000000000..a95d330b3a5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ONestedMultiValueChangeEvent.java @@ -0,0 +1,32 @@ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine; + +/** + * Created by tglman on 11/03/16. + */ +public class ONestedMultiValueChangeEvent extends OMultiValueChangeEvent { + + private OMultiValueChangeTimeLine timeLine; + + public ONestedMultiValueChangeEvent(K key, V value) { + super(OChangeType.NESTED, key, value); + } + + public ONestedMultiValueChangeEvent(K key, V value, V oldValue) { + super(OChangeType.NESTED, key, value, oldValue); + } + + public ONestedMultiValueChangeEvent(K key, V value, V oldValue, boolean changesOwnerContent) { + super(OChangeType.NESTED, key, value, oldValue, changesOwnerContent); + } + + public OMultiValueChangeTimeLine getTimeLine() { + return timeLine; + } + + public void setTimeLine(OMultiValueChangeTimeLine timeLine) { + this.timeLine = timeLine; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytes.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytes.java new file mode 100755 index 00000000000..f42f5481e52 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytes.java @@ -0,0 +1,186 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecordAbstract; +import com.orientechnologies.orient.core.serialization.OMemoryStream; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerRaw; + +/** + * The rawest representation of a record. It's schema less. Use this if you need to store Strings or byte[] without matter about the + * content. Useful also to store multimedia contents and binary files. The object can be reused across calls to the database by + * using the reset() at every re-use. + */ +@SuppressWarnings({ "unchecked" }) +public class ORecordBytes extends ORecordAbstract implements OBlob { + private static final long serialVersionUID = 1L; + + private static final byte[] EMPTY_SOURCE = new byte[] {}; + + public ORecordBytes() { + setup(); + } + + public ORecordBytes(final ODatabaseDocumentInternal iDatabase) { + setup(); + ODatabaseRecordThreadLocal.INSTANCE.set(iDatabase); + } + + public ORecordBytes(final ODatabaseDocumentInternal iDatabase, final byte[] iSource) { + this(iSource); + ODatabaseRecordThreadLocal.INSTANCE.set(iDatabase); + } + + public ORecordBytes(final byte[] iSource) { + super(iSource); + _dirty = true; + _contentChanged = true; + setup(); + } + + public ORecordBytes(final ORID iRecordId) { + _recordId = (ORecordId) iRecordId; + setup(); + } + + public ORecordBytes reset(final byte[] iSource) { + reset(); + _source = iSource; + return this; + } + + public ORecordBytes copy() { + return (ORecordBytes) copyTo(new ORecordBytes()); + } + + @Override + public ORecordBytes fromStream(final byte[] iRecordBuffer) { + _source = iRecordBuffer; + _status = ORecordElement.STATUS.LOADED; + return this; + } + + @Override + public ORecordAbstract clear() { + clearSource(); + return super.clear(); + } + + @Override + public byte[] toStream() { + return _source; + } + + public byte getRecordType() { + return RECORD_TYPE; + } + + @Override + protected void setup() { + super.setup(); + _recordFormat = ORecordSerializerFactory.instance().getFormat(ORecordSerializerRaw.NAME); + } + + /** + * Reads the input stream in memory. This is less efficient than {@link #fromInputStream(InputStream, int)} because allocation is + * made multiple times. If you already know the input size use {@link #fromInputStream(InputStream, int)}. + * + * @param in + * Input Stream, use buffered input stream wrapper to speed up reading + * @return Buffer read from the stream. It's also the internal buffer size in bytes + * @throws IOException + */ + public int fromInputStream(final InputStream in) throws IOException { + final OMemoryStream out = new OMemoryStream(); + try { + final byte[] buffer = new byte[OMemoryStream.DEF_SIZE]; + int readBytesCount; + while (true) { + readBytesCount = in.read(buffer, 0, buffer.length); + if (readBytesCount == -1) { + break; + } + out.write(buffer, 0, readBytesCount); + } + out.flush(); + _source = out.toByteArray(); + } finally { + out.close(); + } + _size = _source.length; + return _size; + } + + /** + * Reads the input stream in memory specifying the maximum bytes to read. This is more efficient than + * {@link #fromInputStream(InputStream)} because allocation is made only once. + * + * @param in + * Input Stream, use buffered input stream wrapper to speed up reading + * @param maxSize + * Maximum size to read + * @return Buffer count of bytes that are read from the stream. It's also the internal buffer size in bytes + * @throws IOException + * if an I/O error occurs. + */ + public int fromInputStream(final InputStream in, final int maxSize) throws IOException { + final byte[] buffer = new byte[maxSize]; + int totalBytesCount = 0; + int readBytesCount; + while (totalBytesCount < maxSize) { + readBytesCount = in.read(buffer, totalBytesCount, buffer.length - totalBytesCount); + if (readBytesCount == -1) { + break; + } + totalBytesCount += readBytesCount; + } + + if (totalBytesCount == 0) { + _source = EMPTY_SOURCE; + _size = 0; + } else if (totalBytesCount == maxSize) { + _source = buffer; + _size = maxSize; + } else { + _source = Arrays.copyOf(buffer, totalBytesCount); + _size = totalBytesCount; + } + return _size; + } + + public void toOutputStream(final OutputStream out) throws IOException { + checkForLoading(); + + if (_source.length > 0) { + out.write(_source); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytesLazy.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytesLazy.java new file mode 100644 index 00000000000..e7b8f767cad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordBytesLazy.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +/** + * Extension of ORecordBytes that handle lazy serialization and converts temporary links (record id in transactions) to finals. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + * Depecraded since v2.2 + * + */ +@SuppressWarnings({ "unchecked", "serial" }) +@Deprecated +public class ORecordBytesLazy extends ORecordBytes { + private OSerializableStream serializableContent; + + public ORecordBytesLazy() { + } + + public ORecordBytesLazy(final OSerializableStream iSerializable) { + this.serializableContent = iSerializable; + } + + @Override + public byte[] toStream() { + if (_source == null) + _source = serializableContent.toStream(); + return _source; + } + + @Override + public ORecordBytesLazy copy() { + final ORecordBytesLazy c = (ORecordBytesLazy) copyTo(new ORecordBytesLazy(serializableContent)); + return c; + } + + public OSerializableStream getSerializableContent() { + return serializableContent; + } + + public void recycle(final OSerializableStream iSerializableContent) { + this.serializableContent = iSerializableContent; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordFlat.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordFlat.java new file mode 100644 index 00000000000..c5db8b322f7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/ORecordFlat.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordAbstract; +import com.orientechnologies.orient.core.record.ORecordStringable; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +/** + * It's schema less. Use this if you need to store Strings at low level. The object can be reused across calls to the database by + * using the reset() at every re-use. + */ +@SuppressWarnings({ "unchecked" }) +@Deprecated +public class ORecordFlat extends ORecordAbstract implements ORecordStringable { + private static final long serialVersionUID = 1L; + public static final byte RECORD_TYPE = 'f'; + protected String value; + + public ORecordFlat(ODatabaseDocumentInternal iDatabase) { + this(); + ODatabaseRecordThreadLocal.INSTANCE.set(iDatabase); + } + + public ORecordFlat() { + setup(); + } + + public ORecordFlat(final byte[] iSource) { + super(iSource); + setup(); + } + + public ORecordFlat(final ODatabaseDocument iDatabase, final ORID iRID) { + _recordId = (ORecordId) iRID; + } + + public ORecordFlat value(final String iValue) { + value = iValue; + setDirty(); + return this; + } + + @Override + public ORecordFlat reset() { + super.reset(); + value = null; + return this; + } + + @Override + public ORecordFlat unload() { + super.unload(); + value = null; + return this; + } + + @Override + public ORecordFlat clear() { + super.clear(); + value = null; + return this; + } + + public ORecordFlat copy() { + ORecordFlat cloned = new ORecordFlat(); + cloned._source = _source; + cloned.value = value; + cloned._recordId = _recordId.copy(); + cloned._dirty = _dirty; + cloned._contentChanged = _contentChanged; + cloned._recordVersion = _recordVersion; + return cloned; + } + + public String value() { + if (value == null) { + // LAZY DESERIALIZATION + if (_source == null && getIdentity() != null && getIdentity().isValid()) + reload(); + + // LAZY LOADING: LOAD THE RECORD FIRST + value = OBinaryProtocol.bytes2string(_source); + } + + return value; + } + + @Override + public String toString() { + return super.toString() + " " + value(); + } + + @Override + public ORecord reload() { + value = null; + return super.reload(); + } + + @Override + public ORecordAbstract fromStream(final byte[] iRecordBuffer) { + super.fromStream(iRecordBuffer); + value = null; + return this; + } + + @Override + public byte[] toStream() { + if (_source == null && value != null) + _source = OBinaryProtocol.string2bytes(value); + return _source; + } + + public int size() { + final String v = value(); + return v != null ? v.length() : 0; + } + + public byte getRecordType() { + return RECORD_TYPE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/record/impl/OSimpleMultiValueChangeListener.java b/core/src/main/java/com/orientechnologies/orient/core/record/impl/OSimpleMultiValueChangeListener.java new file mode 100644 index 00000000000..973e457c297 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/record/impl/OSimpleMultiValueChangeListener.java @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.record.impl; + +import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeListener; +import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine; +import com.orientechnologies.orient.core.db.record.ORecordElement.STATUS; + +import java.lang.ref.WeakReference; + +/** + * Perform gathering of all operations performed on tracked collection and create mapping between list of collection operations and + * field name that contains collection that was changed. + * + * @param Value that uniquely identifies position of item in collection + * @param Item value. + */ +final class OSimpleMultiValueChangeListener implements OMultiValueChangeListener { + /** + * + */ + private final WeakReference oDocument; + private final ODocumentEntry entry; + + OSimpleMultiValueChangeListener(ODocument oDocument, final ODocumentEntry entry) { + this.oDocument = new WeakReference(oDocument); + this.entry = entry; + } + + public void onAfterRecordChanged(final OMultiValueChangeEvent event) { + ODocument document = oDocument.get(); + if (document == null) + //doc not alive anymore, do nothing. + return; + if (document.getInternalStatus() != STATUS.UNMARSHALLING) { + if (event.isChangesOwnerContent()) + document.setDirty(); + else + document.setDirtyNoChanged(); + } + + if (!(document._trackingChanges && document.getIdentity().isValid()) || document.getInternalStatus() == STATUS.UNMARSHALLING) + return; + + if (entry == null || entry.isChanged()) + return; + + if (entry.timeLine == null) { + entry.timeLine = new OMultiValueChangeTimeLine(); + } + + entry.timeLine.addCollectionChangeEvent((OMultiValueChangeEvent) event); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationError.java b/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationError.java new file mode 100644 index 00000000000..3dcd6725344 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationError.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.replication; + +/** + * Interface to catch errors on asynchronous replication. + * + * @author Luca Garulli + */ +public interface OAsyncReplicationError { + enum ACTION {IGNORE, RETRY} + + /** + * Callback called in case of error during asynchronous replication. + * + * @param iException The exception caught + * @param iRetry The number of retries so far. At every retry, this number is incremented. + * @return RETRY to retry the operation, otherwise IGNORE + */ + ACTION onAsyncReplicationError(Throwable iException, int iRetry); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationOk.java b/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationOk.java new file mode 100644 index 00000000000..1a43cede3d0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/replication/OAsyncReplicationOk.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.replication; + +/** + * Interface to catch asynchronous replication operation successfully completed. + * + * @author Luca Garulli + */ +public interface OAsyncReplicationOk { + void onAsyncReplicationOk(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OCronExpression.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OCronExpression.java new file mode 100755 index 00000000000..b13a95fe154 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OCronExpression.java @@ -0,0 +1,1572 @@ +/* + * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import java.io.Serializable; +import java.text.ParseException; +import java.util.*; + +/** + * Provides a parser and evaluator for unix-like cron expressions. Cron expressions provide the ability to specify complex time + * combinations such as "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month". + *

          + * Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields respectively are + * described as follows: + *

          + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
          Field Name Allowed Values Allowed Special Characters
          Seconds  + * 0-59  + * , - * /
          Minutes  + * 0-59  + * , - * /
          Hours  + * 0-23  + * , - * /
          Day-of-month  + * 1-31  + * , - * ? / L W
          Month  + * 1-12 or JAN-DEC  + * , - * /
          Day-of-Week  + * 1-7 or SUN-SAT  + * , - * ? / L #
          Year (Optional)  + * empty, 1970-2199  + * , - * /
          + *

          + * The '*' character is used to specify all values. For example, "*" in the minute field means "every minute". + *

          + * The '?' character is allowed for the day-of-month and day-of-week fields. It is used to specify 'no specific value'. This is + * useful when you need to specify something in one of the two fields, but not the other. + *

          + * The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and + * 12". + *

          + * The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field means + * "the days Monday, Wednesday, and Friday". + *

          + * The '/' character is used to specify increments. For example "0/15" in the seconds field means "the seconds 0, 15, + * 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". Specifying '*' + * before the '/' is equivalent to specifying 0 is the value to start with. Essentially, for each field in the expression, there is + * a set of numbers that can be turned on or off. For seconds and minutes, the numbers range from 0 to 59. For hours 0 to 23, for + * days of the month 0 to 31, and for months 1 to 12. The "/" character simply helps you turn on every "nth" + * value in the given set. Thus "7/6" in the month field only turns on month "7", it does NOT mean every 6th + * month, please note that subtlety. + *

          + * The 'L' character is allowed for the day-of-month and day-of-week fields. This character is short-hand for "last", but + * it has different meaning in each of the two fields. For example, the value "L" in the day-of-month field means + * "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week + * field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it + * means "the last xxx day of the month" - for example "6L" means "the last friday of the month". You + * can also specify an offset from the last day of the month, such as "L-3" which would mean the third-to-last day of the calendar + * month. When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get + * confusing/unexpected results. + *

          + * The 'W' character is allowed for the day-of-month field. This character is used to specify the weekday (Monday-Friday) nearest + * the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: + * "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. + * If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the + * 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on + * Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the + * day-of-month is a single day, not a range or list of days. + *

          + * The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', which translates to "last + * weekday of the month". + *

          + * The '#' character is allowed for the day-of-week field. This character is used to specify "the nth" XXX day of the + * month. For example, the value of "6#3" in the day-of-week field means the third Friday of the month (day 6 = Friday and + * "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = + * the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the + * month, then no firing will occur that month. If the '#' character is used, there can only be one expression in the day-of-week + * field ("3#1,6#3" is not valid, since there are two expressions). + *

          + * + *

          + * The legal characters and the names of months and days of the week are not case sensitive. + *

          + *

          + * NOTES: + *

            + *
          • Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' character in + * one of these fields).
          • + *
          • Overflowing ranges is supported - that is, having a larger number on the left hand side than the right. You might do 22-2 to + * catch 10 o'clock at night until 2 o'clock in the morning, or you might have NOV-FEB. It is very important to note that overuse of + * overflowing ranges creates ranges that don't make sense and no effort has been made to determine which interpretation + * CronExpression chooses. An example would be "0 0 14-6 ? * FRI-MON".
          • + *
          + *

          + * + * @author Sharada Jambula, James House + * @author Contributions from Mads Henderson + * @author Refactoring from CronTrigger to CronExpression by Aaron Craven + */ +public final class OCronExpression implements Serializable, Cloneable { + + private static final long serialVersionUID = 12423409423L; + + protected static final int SECOND = 0; + protected static final int MINUTE = 1; + protected static final int HOUR = 2; + protected static final int DAY_OF_MONTH = 3; + protected static final int MONTH = 4; + protected static final int DAY_OF_WEEK = 5; + protected static final int YEAR = 6; + protected static final int ALL_SPEC_INT = 99; // '*' + protected static final int NO_SPEC_INT = 98; // '?' + protected static final Integer ALL_SPEC = ALL_SPEC_INT; + protected static final Integer NO_SPEC = NO_SPEC_INT; + + protected static final Map monthMap = new HashMap(20); + protected static final Map dayMap = new HashMap(60); + + static { + monthMap.put("JAN", 0); + monthMap.put("FEB", 1); + monthMap.put("MAR", 2); + monthMap.put("APR", 3); + monthMap.put("MAY", 4); + monthMap.put("JUN", 5); + monthMap.put("JUL", 6); + monthMap.put("AUG", 7); + monthMap.put("SEP", 8); + monthMap.put("OCT", 9); + monthMap.put("NOV", 10); + monthMap.put("DEC", 11); + + dayMap.put("SUN", 1); + dayMap.put("MON", 2); + dayMap.put("TUE", 3); + dayMap.put("WED", 4); + dayMap.put("THU", 5); + dayMap.put("FRI", 6); + dayMap.put("SAT", 7); + } + + private final String cronExpression; + private TimeZone timeZone = null; + protected transient TreeSet seconds; + protected transient TreeSet minutes; + protected transient TreeSet hours; + protected transient TreeSet daysOfMonth; + protected transient TreeSet months; + protected transient TreeSet daysOfWeek; + protected transient TreeSet years; + + protected transient boolean lastdayOfWeek = false; + protected transient int nthdayOfWeek = 0; + protected transient boolean lastdayOfMonth = false; + protected transient boolean nearestWeekday = false; + protected transient int lastdayOffset = 0; + protected transient boolean expressionParsed = false; + + public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; + + private class ValueSet { + public int value; + public int pos; + } + + /** + * Constructs a new CronExpression based on the specified parameter. + * + * @param cronExpression String representation of the cron expression the new object should represent + * @throws java.text.ParseException if the string expression cannot be parsed into a valid CronExpression + */ + public OCronExpression(String cronExpression) throws ParseException { + if (cronExpression == null) { + throw new IllegalArgumentException("cronExpression cannot be null"); + } + + this.cronExpression = cronExpression.toUpperCase(Locale.US); + + buildExpression(this.cronExpression); + } + + /** + * Constructs a new {@code CronExpression} as a copy of an existing instance. + * + * @param expression The existing cron expression to be copied + */ + public OCronExpression(OCronExpression expression) { + /* + * We don't call the other constructor here since we need to swallow the ParseException. We also elide some of the sanity + * checking as it is not logically trippable. + */ + this.cronExpression = expression.getCronExpression(); + try { + buildExpression(cronExpression); + } catch (ParseException ex) { + throw new AssertionError(ex); + } + if (expression.getTimeZone() != null) { + setTimeZone((TimeZone) expression.getTimeZone().clone()); + } + } + + /** + * Indicates whether the given date satisfies the cron expression. Note that milliseconds are ignored, so two Dates falling on + * different milliseconds of the same second will always have the same result here. + * + * @param date the date to evaluate + * @return a boolean indicating whether the given date satisfies the cron expression + */ + public boolean isSatisfiedBy(Date date) { + Calendar testDateCal = Calendar.getInstance(getTimeZone()); + testDateCal.setTime(date); + testDateCal.set(Calendar.MILLISECOND, 0); + Date originalDate = testDateCal.getTime(); + + testDateCal.add(Calendar.SECOND, -1); + + Date timeAfter = getTimeAfter(testDateCal.getTime()); + + return ((timeAfter != null) && (timeAfter.equals(originalDate))); + } + + /** + * Returns the next date/time after the given date/time which satisfies the cron expression. + * + * @param date the date/time at which to begin the search for the next valid date/time + * @return the next valid date/time + */ + public Date getNextValidTimeAfter(Date date) { + return getTimeAfter(date); + } + + /** + * Returns the next date/time after the given date/time which does not satisfy the expression + * + * @param date the date/time at which to begin the search for the next invalid date/time + * @return the next valid date/time + */ + public Date getNextInvalidTimeAfter(Date date) { + long difference = 1000; + + // move back to the nearest second so differences will be accurate + Calendar adjustCal = Calendar.getInstance(getTimeZone()); + adjustCal.setTime(date); + adjustCal.set(Calendar.MILLISECOND, 0); + Date lastDate = adjustCal.getTime(); + + Date newDate; + + // FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, + // depending on the cron expression. It is, however A solution. + + // keep getting the next included time until it's farther than one second + // apart. At that point, lastDate is the last valid fire time. We return + // the second immediately following it. + while (difference == 1000) { + newDate = getTimeAfter(lastDate); + if (newDate == null) + break; + + difference = newDate.getTime() - lastDate.getTime(); + + if (difference == 1000) { + lastDate = newDate; + } + } + + return new Date(lastDate.getTime() + 1000); + } + + /** + * Returns the time zone for which this CronExpression will be resolved. + */ + public TimeZone getTimeZone() { + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + + return timeZone; + } + + /** + * Sets the time zone for which this CronExpression will be resolved. + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Returns the string representation of the CronExpression + * + * @return a string representation of the CronExpression + */ + @Override + public String toString() { + return cronExpression; + } + + /** + * Indicates whether the specified cron expression can be parsed into a valid cron expression + * + * @param cronExpression the expression to evaluate + * @return a boolean indicating whether the given expression is a valid cron expression + */ + public static boolean isValidExpression(String cronExpression) { + + try { + new OCronExpression(cronExpression); + } catch (ParseException pe) { + return false; + } + + return true; + } + + public static void validateExpression(String cronExpression) throws ParseException { + + new OCronExpression(cronExpression); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Expression Parsing Functions + // + //////////////////////////////////////////////////////////////////////////// + + protected synchronized void buildExpression(String expression) throws ParseException { + expressionParsed = true; + + try { + + if (seconds == null) { + seconds = new TreeSet(); + } + if (minutes == null) { + minutes = new TreeSet(); + } + if (hours == null) { + hours = new TreeSet(); + } + if (daysOfMonth == null) { + daysOfMonth = new TreeSet(); + } + if (months == null) { + months = new TreeSet(); + } + if (daysOfWeek == null) { + daysOfWeek = new TreeSet(); + } + if (years == null) { + years = new TreeSet(); + } + + int exprOn = SECOND; + + StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); + + while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { + String expr = exprsTok.nextToken().trim(); + + // throw an exception if L is used with other days of the month + if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { + throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); + } + // throw an exception if L is used with other days of the week + if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { + throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); + } + if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) { + throw new ParseException("Support for specifying multiple \"nth\" days is not implemented", -1); + } + + StringTokenizer vTok = new StringTokenizer(expr, ","); + while (vTok.hasMoreTokens()) { + String v = vTok.nextToken(); + storeExpressionVals(0, v, exprOn); + } + + exprOn++; + } + + if (exprOn <= DAY_OF_WEEK) { + throw new ParseException("Unexpected end of expression.", expression.length()); + } + + if (exprOn <= YEAR) { + storeExpressionVals(0, "*", YEAR); + } + + TreeSet dow = getSet(DAY_OF_WEEK); + TreeSet dom = getSet(DAY_OF_MONTH); + + // Copying the logic from the UnsupportedOperationException below + boolean dayOfMSpec = !dom.contains(NO_SPEC); + boolean dayOfWSpec = !dow.contains(NO_SPEC); + + if (!dayOfMSpec || dayOfWSpec) { + if (!dayOfWSpec || dayOfMSpec) { + throw new ParseException("Support for specifying both a day-of-week AND a day-of-month parameter is not implemented", 0); + } + } + } catch (ParseException pe) { + throw pe; + } catch (Exception e) { + throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0); + } + } + + protected int storeExpressionVals(int pos, String s, int type) throws ParseException { + + int incr = 0; + int i = skipWhiteSpace(pos, s); + if (i >= s.length()) { + return i; + } + char c = s.charAt(i); + if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) { + String sub = s.substring(i, i + 3); + int sval = -1; + int eval = -1; + if (type == MONTH) { + sval = getMonthNumber(sub) + 1; + if (sval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getMonthNumber(sub) + 1; + if (eval <= 0) { + throw new ParseException("Invalid Month value: '" + sub + "'", i); + } + } + } + } else if (type == DAY_OF_WEEK) { + sval = getDayOfWeekNumber(sub); + if (sval < 0) { + throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); + } + if (s.length() > i + 3) { + c = s.charAt(i + 3); + if (c == '-') { + i += 4; + sub = s.substring(i, i + 3); + eval = getDayOfWeekNumber(sub); + if (eval < 0) { + throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); + } + } else if (c == '#') { + try { + i += 4; + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } catch (Exception e) { + throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i); + } + } else if (c == 'L') { + lastdayOfWeek = true; + i++; + } + } + + } else { + throw new ParseException("Illegal characters for this position: '" + sub + "'", i); + } + if (eval != -1) { + incr = 1; + } + addToSet(sval, eval, incr, type); + return (i + 3); + } + + if (c == '?') { + i++; + if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { + throw new ParseException("Illegal character after '?': " + s.charAt(i), i); + } + if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { + throw new ParseException("'?' can only be specfied for Day-of-Month or Day-of-Week.", i); + } + if (type == DAY_OF_WEEK && !lastdayOfMonth) { + int val = daysOfMonth.last(); + if (val == NO_SPEC_INT) { + throw new ParseException("'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", i); + } + } + + addToSet(NO_SPEC_INT, -1, 0, type); + return i; + } + + if (c == '*' || c == '/') { + if (c == '*' && (i + 1) >= s.length()) { + addToSet(ALL_SPEC_INT, -1, incr, type); + return i + 1; + } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) { + throw new ParseException("'/' must be followed by an integer", i); + } else if (c == '*') { + i++; + } + c = s.charAt(i); + if (c == '/') { // is an increment specified? + i++; + if (i >= s.length()) { + throw new ParseException("Unexpected end of string", i); + } + + incr = getNumericValue(s, i); + + i++; + if (incr > 10) { + i++; + } + if (incr > 59 && (type == SECOND || type == MINUTE)) { + throw new ParseException("Increment > 60 : " + incr, i); + } else if (incr > 23 && (type == HOUR)) { + throw new ParseException("Increment > 24 : " + incr, i); + } else if (incr > 31 && (type == DAY_OF_MONTH)) { + throw new ParseException("Increment > 31 : " + incr, i); + } else if (incr > 7 && (type == DAY_OF_WEEK)) { + throw new ParseException("Increment > 7 : " + incr, i); + } else if (incr > 12 && (type == MONTH)) { + throw new ParseException("Increment > 12 : " + incr, i); + } + } else { + incr = 1; + } + + addToSet(ALL_SPEC_INT, -1, incr, type); + return i; + } else if (c == 'L') { + i++; + if (type == DAY_OF_MONTH) { + lastdayOfMonth = true; + } + if (type == DAY_OF_WEEK) { + addToSet(7, 7, 0, type); + } + if (type == DAY_OF_MONTH && s.length() > i) { + c = s.charAt(i); + if (c == '-') { + ValueSet vs = getValue(0, s, i + 1); + lastdayOffset = vs.value; + if (lastdayOffset > 30) + throw new ParseException("Offset from last day must be <= 30", i + 1); + i = vs.pos; + } + if (s.length() > i) { + c = s.charAt(i); + if (c == 'W') { + nearestWeekday = true; + i++; + } + } + } + return i; + } else if (c >= '0' && c <= '9') { + int val = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, -1, -1, type); + } else { + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(val, s, i); + val = vs.value; + i = vs.pos; + } + i = checkNext(i, s, val, type); + return i; + } + } else { + throw new ParseException("Unexpected character: " + c, i); + } + + return i; + } + + protected int checkNext(int pos, String s, int val, int type) throws ParseException { + + int end = -1; + int i = pos; + + if (i >= s.length()) { + addToSet(val, end, -1, type); + return i; + } + + char c = s.charAt(pos); + + if (c == 'L') { + if (type == DAY_OF_WEEK) { + if (val < 1 || val > 7) + throw new ParseException("Day-of-Week values must be between 1 and 7", -1); + lastdayOfWeek = true; + } else { + throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); + } + TreeSet set = getSet(type); + set.add(val); + i++; + return i; + } + + if (c == 'W') { + if (type == DAY_OF_MONTH) { + nearestWeekday = true; + } else { + throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); + } + if (val > 31) + throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", + i); + TreeSet set = getSet(type); + set.add(val); + i++; + return i; + } + + if (c == '#') { + if (type != DAY_OF_WEEK) { + throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); + } + i++; + try { + nthdayOfWeek = Integer.parseInt(s.substring(i)); + if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + throw new Exception(); + } + } catch (Exception e) { + throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i); + } + + TreeSet set = getSet(type); + set.add(val); + i++; + return i; + } + + if (c == '-') { + i++; + c = s.charAt(i); + int v = Integer.parseInt(String.valueOf(c)); + end = v; + i++; + if (i >= s.length()) { + addToSet(val, end, 1, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v, s, i); + end = vs.value; + i = vs.pos; + } + if (i < s.length() && ((c = s.charAt(i)) == '/')) { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } else { + addToSet(val, end, v2, type); + return i; + } + } else { + addToSet(val, end, 1, type); + return i; + } + } + + if (c == '/') { + i++; + c = s.charAt(i); + int v2 = Integer.parseInt(String.valueOf(c)); + i++; + if (i >= s.length()) { + addToSet(val, end, v2, type); + return i; + } + c = s.charAt(i); + if (c >= '0' && c <= '9') { + ValueSet vs = getValue(v2, s, i); + int v3 = vs.value; + addToSet(val, end, v3, type); + i = vs.pos; + return i; + } else { + throw new ParseException("Unexpected character '" + c + "' after '/'", i); + } + } + + addToSet(val, end, 0, type); + i++; + return i; + } + + public String getCronExpression() { + return cronExpression; + } + + public String getExpressionSummary() { + StringBuilder buf = new StringBuilder(); + + buf.append("seconds: "); + buf.append(getExpressionSetSummary(seconds)); + buf.append("\n"); + buf.append("minutes: "); + buf.append(getExpressionSetSummary(minutes)); + buf.append("\n"); + buf.append("hours: "); + buf.append(getExpressionSetSummary(hours)); + buf.append("\n"); + buf.append("daysOfMonth: "); + buf.append(getExpressionSetSummary(daysOfMonth)); + buf.append("\n"); + buf.append("months: "); + buf.append(getExpressionSetSummary(months)); + buf.append("\n"); + buf.append("daysOfWeek: "); + buf.append(getExpressionSetSummary(daysOfWeek)); + buf.append("\n"); + buf.append("lastdayOfWeek: "); + buf.append(lastdayOfWeek); + buf.append("\n"); + buf.append("nearestWeekday: "); + buf.append(nearestWeekday); + buf.append("\n"); + buf.append("NthDayOfWeek: "); + buf.append(nthdayOfWeek); + buf.append("\n"); + buf.append("lastdayOfMonth: "); + buf.append(lastdayOfMonth); + buf.append("\n"); + buf.append("years: "); + buf.append(getExpressionSetSummary(years)); + buf.append("\n"); + + return buf.toString(); + } + + protected String getExpressionSetSummary(java.util.Set set) { + + if (set.contains(NO_SPEC)) { + return "?"; + } + if (set.contains(ALL_SPEC)) { + return "*"; + } + + StringBuilder buf = new StringBuilder(); + + Iterator itr = set.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected String getExpressionSetSummary(java.util.ArrayList list) { + + if (list.contains(NO_SPEC)) { + return "?"; + } + if (list.contains(ALL_SPEC)) { + return "*"; + } + + StringBuilder buf = new StringBuilder(); + + Iterator itr = list.iterator(); + boolean first = true; + while (itr.hasNext()) { + Integer iVal = itr.next(); + String val = iVal.toString(); + if (!first) { + buf.append(","); + } + buf.append(val); + first = false; + } + + return buf.toString(); + } + + protected int skipWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { + ; + } + + return i; + } + + protected int findNextWhiteSpace(int i, String s) { + for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { + ; + } + + return i; + } + + protected void addToSet(int val, int end, int incr, int type) throws ParseException { + + TreeSet set = getSet(type); + + if (type == SECOND || type == MINUTE) { + if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { + throw new ParseException("Minute and Second values must be between 0 and 59", -1); + } + } else if (type == HOUR) { + if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { + throw new ParseException("Hour values must be between 0 and 23", -1); + } + } else if (type == DAY_OF_MONTH) { + if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { + throw new ParseException("Day of month values must be between 1 and 31", -1); + } + } else if (type == MONTH) { + if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { + throw new ParseException("Month values must be between 1 and 12", -1); + } + } else if (type == DAY_OF_WEEK) { + if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { + throw new ParseException("Day-of-Week values must be between 1 and 7", -1); + } + } + + if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { + if (val != -1) { + set.add(val); + } else { + set.add(NO_SPEC); + } + + return; + } + + int startAt = val; + int stopAt = end; + + if (val == ALL_SPEC_INT && incr <= 0) { + incr = 1; + set.add(ALL_SPEC); // put in a marker, but also fill values + } + + if (type == SECOND || type == MINUTE) { + if (stopAt == -1) { + stopAt = 59; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } else if (type == HOUR) { + if (stopAt == -1) { + stopAt = 23; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 0; + } + } else if (type == DAY_OF_MONTH) { + if (stopAt == -1) { + stopAt = 31; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == MONTH) { + if (stopAt == -1) { + stopAt = 12; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == DAY_OF_WEEK) { + if (stopAt == -1) { + stopAt = 7; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1; + } + } else if (type == YEAR) { + if (stopAt == -1) { + stopAt = MAX_YEAR; + } + if (startAt == -1 || startAt == ALL_SPEC_INT) { + startAt = 1970; + } + } + + // if the end of the range is before the start, then we need to overflow into + // the next day, month etc. This is done by adding the maximum amount for that + // type, and using modulus max to determine the value being added. + int max = -1; + if (stopAt < startAt) { + switch (type) { + case SECOND: + max = 60; + break; + case MINUTE: + max = 60; + break; + case HOUR: + max = 24; + break; + case MONTH: + max = 12; + break; + case DAY_OF_WEEK: + max = 7; + break; + case DAY_OF_MONTH: + max = 31; + break; + case YEAR: + throw new IllegalArgumentException("Start year must be less than stop year"); + default: + throw new IllegalArgumentException("Unexpected type encountered"); + } + stopAt += max; + } + + for (int i = startAt; i <= stopAt; i += incr) { + if (max == -1) { + // ie: there's no max to overflow over + set.add(i); + } else { + // take the modulus to get the real value + int i2 = i % max; + + // 1-indexed ranges should not include 0, and should include their max + if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { + i2 = max; + } + + set.add(i2); + } + } + } + + TreeSet getSet(int type) { + switch (type) { + case SECOND: + return seconds; + case MINUTE: + return minutes; + case HOUR: + return hours; + case DAY_OF_MONTH: + return daysOfMonth; + case MONTH: + return months; + case DAY_OF_WEEK: + return daysOfWeek; + case YEAR: + return years; + default: + return null; + } + } + + protected ValueSet getValue(int v, String s, int i) { + char c = s.charAt(i); + StringBuilder s1 = new StringBuilder(String.valueOf(v)); + while (c >= '0' && c <= '9') { + s1.append(c); + i++; + if (i >= s.length()) { + break; + } + c = s.charAt(i); + } + ValueSet val = new ValueSet(); + + val.pos = (i < s.length()) ? i : i + 1; + val.value = Integer.parseInt(s1.toString()); + return val; + } + + protected int getNumericValue(String s, int i) { + int endOfVal = findNextWhiteSpace(i, s); + String val = s.substring(i, endOfVal); + return Integer.parseInt(val); + } + + protected int getMonthNumber(String s) { + Integer integer = monthMap.get(s); + + if (integer == null) { + return -1; + } + + return integer; + } + + protected int getDayOfWeekNumber(String s) { + Integer integer = dayMap.get(s); + + if (integer == null) { + return -1; + } + + return integer; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Computation Functions + // + //////////////////////////////////////////////////////////////////////////// + + public synchronized Date getTimeAfter(Date afterTime) { + + // Computation is based on Gregorian year only. + Calendar cl = new java.util.GregorianCalendar(getTimeZone()); + + // move ahead one second, since we're computing the time *after* the + // given time + afterTime = new Date(afterTime.getTime() + 1000); + // CronTrigger does not deal with milliseconds + cl.setTime(afterTime); + cl.set(Calendar.MILLISECOND, 0); + + boolean gotOne = false; + // loop until we've computed the next time, or we've past the endTime + while (!gotOne) { + + // if (endTime != null && cl.getTime().after(endTime)) return null; + if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... + return null; + } + + SortedSet st = null; + int t = 0; + + int sec = cl.get(Calendar.SECOND); + int min = cl.get(Calendar.MINUTE); + + // get second................................................. + st = seconds.tailSet(sec); + if (st != null && st.size() != 0) { + sec = st.first(); + } else { + sec = seconds.first(); + min++; + cl.set(Calendar.MINUTE, min); + } + cl.set(Calendar.SECOND, sec); + + min = cl.get(Calendar.MINUTE); + int hr = cl.get(Calendar.HOUR_OF_DAY); + t = -1; + + // get minute................................................. + st = minutes.tailSet(min); + if (st != null && st.size() != 0) { + t = min; + min = st.first(); + } else { + min = minutes.first(); + hr++; + } + if (min != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, min); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.MINUTE, min); + + hr = cl.get(Calendar.HOUR_OF_DAY); + int day = cl.get(Calendar.DAY_OF_MONTH); + t = -1; + + // get hour................................................... + st = hours.tailSet(hr); + if (st != null && st.size() != 0) { + t = hr; + hr = st.first(); + } else { + hr = hours.first(); + day++; + } + if (hr != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + setCalendarHour(cl, hr); + continue; + } + cl.set(Calendar.HOUR_OF_DAY, hr); + + day = cl.get(Calendar.DAY_OF_MONTH); + int mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + t = -1; + int tmon = mon; + + // get day................................................... + boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); + boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); + if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule + st = daysOfMonth.tailSet(day); + if (lastdayOfMonth) { + if (!nearestWeekday) { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + day -= lastdayOffset; + if (t > day) { + mon++; + if (mon > 12) { + mon = 1; + tmon = 3333; // ensure test of mon != tmon further below fails + cl.add(Calendar.YEAR, 1); + } + day = 1; + } + } else { + t = day; + day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + day -= lastdayOffset; + + java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if (dow == Calendar.SATURDAY && day == 1) { + day += 2; + } else if (dow == Calendar.SATURDAY) { + day -= 1; + } else if (dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } else if (dow == Calendar.SUNDAY) { + day += 1; + } + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if (nTime.before(afterTime)) { + day = 1; + mon++; + } + } + } else if (nearestWeekday) { + t = day; + day = daysOfMonth.first(); + + java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); + tcal.set(Calendar.SECOND, 0); + tcal.set(Calendar.MINUTE, 0); + tcal.set(Calendar.HOUR_OF_DAY, 0); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); + + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + int dow = tcal.get(Calendar.DAY_OF_WEEK); + + if (dow == Calendar.SATURDAY && day == 1) { + day += 2; + } else if (dow == Calendar.SATURDAY) { + day -= 1; + } else if (dow == Calendar.SUNDAY && day == ldom) { + day -= 2; + } else if (dow == Calendar.SUNDAY) { + day += 1; + } + + tcal.set(Calendar.SECOND, sec); + tcal.set(Calendar.MINUTE, min); + tcal.set(Calendar.HOUR_OF_DAY, hr); + tcal.set(Calendar.DAY_OF_MONTH, day); + tcal.set(Calendar.MONTH, mon - 1); + Date nTime = tcal.getTime(); + if (nTime.before(afterTime)) { + day = daysOfMonth.first(); + mon++; + } + } else if (st != null && st.size() != 0) { + t = day; + day = st.first(); + // make sure we don't over-run a short month, such as february + int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + if (day > lastDay) { + day = daysOfMonth.first(); + mon++; + } + } else { + day = daysOfMonth.first(); + mon++; + } + + if (day != t || mon != tmon) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we + // are 1-based + continue; + } + } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule + if (lastdayOfWeek) { // are we looking for the last XXX day of + // the month? + int dow = daysOfWeek.first(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // did we already miss the + // last one? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } + + // find date of last occurrence of this day in this month... + while ((day + daysToAdd + 7) <= lDay) { + daysToAdd += 7; + } + + day += daysToAdd; + + if (daysToAdd > 0) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are not promoting the month + continue; + } + + } else if (nthdayOfWeek != 0) { + // are we looking for the Nth XXX day in the month? + int dow = daysOfWeek.first(); // desired + // d-o-w + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } else if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + boolean dayShifted = false; + if (daysToAdd > 0) { + dayShifted = true; + } + + day += daysToAdd; + int weekOfMonth = day / 7; + if (day % 7 > 0) { + weekOfMonth++; + } + + daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; + day += daysToAdd; + if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl.get(Calendar.YEAR))) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } else if (daysToAdd > 0 || dayShifted) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' here because we are NOT promoting the month + continue; + } + } else { + int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w + int dow = daysOfWeek.first(); // desired + // d-o-w + st = daysOfWeek.tailSet(cDow); + if (st != null && st.size() > 0) { + dow = st.first(); + } + + int daysToAdd = 0; + if (cDow < dow) { + daysToAdd = dow - cDow; + } + if (cDow > dow) { + daysToAdd = dow + (7 - cDow); + } + + int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); + + if (day + daysToAdd > lDay) { // will we pass the end of + // the month? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon); + // no '- 1' here because we are promoting the month + continue; + } else if (daysToAdd > 0) { // are we swithing days? + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, + // and we are 1-based + continue; + } + } + } else { // dayOfWSpec && !dayOfMSpec + throw new UnsupportedOperationException( + "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); + } + cl.set(Calendar.DAY_OF_MONTH, day); + + mon = cl.get(Calendar.MONTH) + 1; + // '+ 1' because calendar is 0-based for this field, and we are + // 1-based + int year = cl.get(Calendar.YEAR); + t = -1; + + // test for expressions that never generate a valid fire date, + // but keep looping... + if (year > MAX_YEAR) { + return null; + } + + // get month................................................... + st = months.tailSet(mon); + if (st != null && st.size() != 0) { + t = mon; + mon = st.first(); + } else { + mon = months.first(); + year++; + } + if (mon != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.MONTH, mon - 1); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + + year = cl.get(Calendar.YEAR); + t = -1; + + // get year................................................... + st = years.tailSet(year); + if (st != null && st.size() != 0) { + t = year; + year = st.first(); + } else { + return null; // ran out of years... + } + + if (year != t) { + cl.set(Calendar.SECOND, 0); + cl.set(Calendar.MINUTE, 0); + cl.set(Calendar.HOUR_OF_DAY, 0); + cl.set(Calendar.DAY_OF_MONTH, 1); + cl.set(Calendar.MONTH, 0); + // '- 1' because calendar is 0-based for this field, and we are + // 1-based + cl.set(Calendar.YEAR, year); + continue; + } + cl.set(Calendar.YEAR, year); + + gotOne = true; + } // while( !done ) + + return cl.getTime(); + } + + /** + * Advance the calendar to the particular hour paying particular attention to daylight saving problems. + * + * @param cal the calendar to operate on + * @param hour the hour to set + */ + protected void setCalendarHour(Calendar cal, int hour) { + cal.set(java.util.Calendar.HOUR_OF_DAY, hour); + if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) { + cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1); + } + } + + /** + * NOT YET IMPLEMENTED: Returns the time before the given time that the CronExpression matches. + */ + public Date getTimeBefore(Date endTime) { + // FUTURE_TODO: implement QUARTZ-423 + return null; + } + + /** + * NOT YET IMPLEMENTED: Returns the final time that the CronExpression will match. + */ + public Date getFinalFireTime() { + // FUTURE_TODO: implement QUARTZ-423 + return null; + } + + protected boolean isLeapYear(int year) { + return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); + } + + protected int getLastDayOfMonth(int monthNum, int year) { + + switch (monthNum) { + case 1: + return 31; + case 2: + return (isLeapYear(year)) ? 29 : 28; + case 3: + return 31; + case 4: + return 30; + case 5: + return 31; + case 6: + return 30; + case 7: + return 31; + case 8: + return 31; + case 9: + return 30; + case 10: + return 31; + case 11: + return 30; + case 12: + return 31; + default: + throw new IllegalArgumentException("Illegal month number: " + monthNum); + } + } + + private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { + + stream.defaultReadObject(); + try { + buildExpression(cronExpression); + } catch (Exception ignore) { + } // never happens + } + + @Override + @Deprecated + public Object clone() { + return new OCronExpression(this); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEvent.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEvent.java new file mode 100755 index 00000000000..3fd146528ff --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEvent.java @@ -0,0 +1,323 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import com.orientechnologies.common.concur.ONeedRetryException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.script.OCommandScriptException; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.schedule.OScheduler.STATUS; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.text.ParseException; +import java.util.Date; +import java.util.Map; +import java.util.TimerTask; + +/** + * Represents an instance of a scheduled event. + * + * @author Luca Garulli + * @author henryzhao81-at-gmail.com + * @since Mar 28, 2013 + */ + +public class OScheduledEvent extends ODocumentWrapper { + public final static String CLASS_NAME = "OSchedule"; + + public static final String PROP_NAME = "name"; + public static final String PROP_RULE = "rule"; + public static final String PROP_ARGUMENTS = "arguments"; + public static final String PROP_STATUS = "status"; + public static final String PROP_FUNC = "function"; + public static final String PROP_STARTTIME = "starttime"; + public static final String PROP_EXEC_ID = "nextExecId"; + + private ODatabaseDocument db; + + private OFunction function; + private boolean isRunning = false; + private OCronExpression cron; + private volatile TimerTask timer; + private long nextExecutionId; + + private class ScheduledTimer extends TimerTask { + @Override + public void run() { + if (isRunning) { + OLogManager.instance().error(this, "Error: The scheduled event '" + getName() + "' is already running"); + return; + } + + if (function == null) { + OLogManager.instance().error(this, "Error: The scheduled event '" + getName() + "' has no configured function"); + return; + } + + try { + + executeFunction(); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (timer != null) { + // RE-SCHEDULE THE NEXT EVENT + schedule(); + } + } + } + } + + /** + * Creates a scheduled event object from a configuration. + */ + public OScheduledEvent(final ODocument doc) { + super(doc); + getFunction(); + try { + cron = new OCronExpression(getRule()); + } catch (ParseException e) { + OLogManager.instance().error(this, "Error on compiling cron expression " + getRule()); + } + } + + public void interrupt() { + final TimerTask t = timer; + timer = null; + if (t != null) + t.cancel(); + } + + public OFunction getFunction() { + final OFunction fun = getFunctionSafe(); + if (fun == null) + throw new OCommandScriptException("Function cannot be null"); + return fun; + } + + public String getRule() { + return document.field(PROP_RULE); + } + + public String getName() { + return document.field(PROP_NAME); + } + + public long getNextExecutionId() { + final Long value = document.field(PROP_EXEC_ID); + if (value == null) + return 0l; + return value; + } + + @Override + public RET save() { + if (db == null) + bindDb(); + else + db.activateOnCurrentThread(); + + return super.save(); + } + + public String getStatus() { + return document.field(PROP_STATUS); + } + + public Map getArguments() { + return document.field(PROP_ARGUMENTS); + } + + public Date getStartTime() { + return document.field(PROP_STARTTIME); + } + + public boolean isRunning() { + return this.isRunning; + } + + public OScheduledEvent schedule() { + if (timer != null) + timer.cancel(); + + bindDb(); + + timer = new ScheduledTimer(); + nextExecutionId = getNextExecutionId() + 1; + Orient.instance().scheduleTask(timer, cron.getNextValidTimeAfter(new Date()), 0); + return this; + } + + public String toString() { + return "OSchedule [name:" + getName() + ",rule:" + getRule() + ",current status:" + getStatus() + ",func:" + getFunctionSafe() + + ",started:" + getStartTime() + "]"; + } + + @Override + public void fromStream(final ODocument iDocument) { + super.fromStream(iDocument); + bindDb(); + try { + cron.buildExpression(getRule()); + } catch (ParseException e) { + OLogManager.instance().error(this, "Error on compiling cron expression " + getRule()); + } + } + + private void executeFunction() { + isRunning = true; + + OLogManager.instance() + .info(this, "Checking for the execution of the scheduled event '%s' executionId=%d...", getName(), nextExecutionId); + + Object result = null; + try { + if (db != null) + db.activateOnCurrentThread(); + + boolean executeEvent = false; + for (int retry = 0; retry < 10; ++retry) { + try { + if (isEventAlreadyExecuted()) + break; + + try { + document.field(PROP_STATUS, STATUS.RUNNING); + document.field(PROP_STARTTIME, System.currentTimeMillis()); + document.field(PROP_EXEC_ID, nextExecutionId); + + document.save(); + + } catch (Exception e) { + OLogManager.instance().error(this, "Error on saving status for event '%s'", e, getName()); + if (!isRunning) { + // EVENT CANCELED + return; + } + } + + // OK + executeEvent = true; + break; + + } catch (ONeedRetryException e) { + + // CONCURRENT UPDATE, PROBABLY EXECUTED BY ANOTHER SERVER + if (isEventAlreadyExecuted()) + break; + + OLogManager.instance() + .info(this, "Cannot change the status of the scheduled event '%s' executionId=%d, retry %d", getName(), + nextExecutionId, retry); + + } catch (ORecordNotFoundException e) { + OLogManager.instance() + .info(this, "Scheduled event '%s' executionId=%d not found on database, removing event", getName(), nextExecutionId); + + timer = null; + break; + + } catch (Throwable e) { + // SUSPEND EXECUTION + OLogManager.instance() + .error(this, "Error during starting of scheduled event '%s' executionId=%d", e, getName(), nextExecutionId); + + timer = null; + break; + } + } + + if (!executeEvent) + return; + + OLogManager.instance().info(this, "Executing scheduled event '%s' executionId=%d...", getName(), nextExecutionId); + try { + result = function.execute(getArguments()); + } finally { + OLogManager.instance() + .info(this, "Scheduled event '%s' executionId=%d completed with result: %s", getName(), nextExecutionId, result); + + try { + document.field(PROP_STATUS, STATUS.WAITING); + document.save(); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on saving status for event '%s'", e, getName()); + } + } + + } finally { + isRunning = false; + } + + return; + } + + private boolean isEventAlreadyExecuted() { + final ORecord rec = document.getIdentity().getRecord(); + if (rec == null) + // SKIP EXECUTION BECAUSE THE EVENT WAS DELETED + return true; + + final ODocument updated = rec.reload(); + + final Long currentExecutionId = updated.field(PROP_EXEC_ID); + if (currentExecutionId == null) + return false; + + if (currentExecutionId >= nextExecutionId) { + OLogManager.instance() + .info(this, "Scheduled event '%s' with id %d is already running (current id=%d)", getName(), nextExecutionId, + currentExecutionId); + // ALREADY RUNNING + return true; + } + return false; + } + + private void bindDb() { + final ODatabaseDocumentInternal tlDb = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (tlDb != null && !tlDb.isClosed()) + this.db = ((ODatabaseDocumentTx) tlDb).copy(); + } + + private OFunction getFunctionSafe() { + if (function == null) { + final Object funcDoc = document.field(PROP_FUNC); + if (funcDoc != null) { + if (funcDoc instanceof OFunction) { + function = (OFunction) funcDoc; + // OVERWRITE FUNCTION ID + document.field(PROP_FUNC, function.getId()); + } else if (funcDoc instanceof ODocument) + function = new OFunction((ODocument) funcDoc); + else if (funcDoc instanceof ORecordId) + function = new OFunction((ORecordId) funcDoc); + } + } + return function; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEventBuilder.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEventBuilder.java new file mode 100755 index 00000000000..35d79e34ec5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduledEventBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.type.ODocumentWrapper; + +import java.util.Date; +import java.util.Map; + +/** + * Builds a OSchedulerEvent with a fluent interface + * + * @author Luca Garulli + * @since v2.2 + */ + +public class OScheduledEventBuilder extends ODocumentWrapper { + public OScheduledEventBuilder() { + super(new ODocument(OScheduledEvent.CLASS_NAME)); + } + + /** + * Creates a scheduled event object from a configuration. + */ + public OScheduledEventBuilder(final ODocument doc) { + super(doc); + } + + public OScheduledEventBuilder setFunction(final OFunction function) { + document.field(OScheduledEvent.PROP_FUNC, function); + return this; + } + + public OScheduledEventBuilder setRule(final String rule) { + document.field(OScheduledEvent.PROP_RULE, rule); + return this; + } + + public OScheduledEventBuilder setArguments(final Map arguments) { + document.field(OScheduledEvent.PROP_ARGUMENTS, arguments); + return this; + } + + public OScheduledEventBuilder setStartTime(final Date startTime) { + document.field(OScheduledEvent.PROP_STARTTIME, startTime); + return this; + } + + public OScheduledEvent build() { + return new OScheduledEvent(document); + } + + public String toString() { + return document.toString(); + } + + public OScheduledEventBuilder setName(final String name) { + document.field(OScheduledEvent.PROP_NAME, name); + return this; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduler.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduler.java new file mode 100644 index 00000000000..4bc9eeac5a5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OScheduler.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import java.util.Map; + +/** + * Scheduler interface. + * + * @author Luca Garulli + * @author henryzhao81-at-gmail.com + * @since Mar 28, 2013 + */ +public interface OScheduler { + enum STATUS { + RUNNING, STOPPED, WAITING + } + + /** + * Creates a new scheduled event. + */ + void scheduleEvent(OScheduledEvent event); + + /** + * Removes a scheduled event. + * + * @param eventName Event's name + */ + void removeEvent(String eventName); + + /** + * Updates a scheduled event. + */ + void updateEvent(OScheduledEvent event); + + /** + * Returns all the scheduled events. + * + * @return + */ + Map getEvents(); + + /** + * Returns a scheduled event by name. + * + * @param eventName Event's name + */ + OScheduledEvent getEvent(String eventName); + + /** + * Loads the scheduled events from database in memory and schedule them. + */ + void load(); + + /** + * Shuts down the scheduler. + */ + void close(); + + /** + * Creates the scheduler classes on database. + */ + void create(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerImpl.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerImpl.java new file mode 100755 index 00000000000..9cfd9b21a39 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerImpl.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Scheduler default implementation. + * + * @author Luca Garulli + * @author henryzhao81-at-gmail.com + * @since Mar 28, 2013 + */ +public class OSchedulerImpl implements OScheduler { + private ConcurrentHashMap events = new ConcurrentHashMap(); + + public OSchedulerImpl() { + } + + @Override + public void scheduleEvent(final OScheduledEvent event) { + if (event.getDocument().getIdentity().isNew()) + // FIST TIME: SAVE IT + event.save(); + + if (events.putIfAbsent(event.getName(), event) == null) + event.schedule(); + } + + @Override + public void removeEvent(final String eventName) { + OLogManager.instance().debug(this, "Removing scheduled event '%s'...", eventName); + + final OScheduledEvent event = events.remove(eventName); + + if (event != null) { + event.interrupt(); + + try { + event.getDocument().reload(); + } catch (ORecordNotFoundException e) { + // ALREADY DELETED, JUST RETURN + return; + } + + // RECORD EXISTS: DELETE THE EVENT RECORD + ODatabaseDocumentTx.executeWithRetries(new OCallable() { + @Override + public Object call(Integer iArgument) { + OLogManager.instance().debug(this, "Deleting scheduled event '%s' rid=%s...", event, event.getDocument().getIdentity()); + try { + event.getDocument().delete(); + } catch (ORecordNotFoundException e) { + // ALREADY DELETED: IGNORE IT + } + return null; + } + }, 10, 0, new ORecord[] { event.getDocument() }); + } + } + + @Override + public void updateEvent(final OScheduledEvent event) { + final OScheduledEvent oldEvent = events.remove(event.getName()); + if (oldEvent != null) + oldEvent.interrupt(); + scheduleEvent(event); + OLogManager.instance().debug(this, "Updated scheduled event '%s' rid=%s...", event, event.getDocument().getIdentity()); + } + + @Override + public Map getEvents() { + return events; + } + + @Override + public OScheduledEvent getEvent(final String name) { + return events.get(name); + } + + @Override + public void load() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + + if (db.getMetadata().getSchema().existsClass(OScheduledEvent.CLASS_NAME)) { + final Iterable result = db.browseClass(OScheduledEvent.CLASS_NAME); + for (ODocument d : result) { + final OScheduledEvent event = new OScheduledEvent(d); + + if (events.putIfAbsent(event.getName(), event) == null) + this.scheduleEvent(event); + } + } + } + + @Override + public void close() { + for (OScheduledEvent event : events.values()) { + event.interrupt(); + } + events.clear(); + } + + @Override + public void create() { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (db.getMetadata().getSchema().existsClass(OScheduledEvent.CLASS_NAME)) + return; + final OClass f = db.getMetadata().getSchema().createClass(OScheduledEvent.CLASS_NAME); + f.createProperty(OScheduledEvent.PROP_NAME, OType.STRING, (OType) null, true).setMandatory(true).setNotNull(true); + f.createProperty(OScheduledEvent.PROP_RULE, OType.STRING, (OType) null, true).setMandatory(true).setNotNull(true); + f.createProperty(OScheduledEvent.PROP_ARGUMENTS, OType.EMBEDDEDMAP, (OType) null, true); + f.createProperty(OScheduledEvent.PROP_STATUS, OType.STRING, (OType) null, true); + f.createProperty(OScheduledEvent.PROP_FUNC, OType.LINK, db.getMetadata().getSchema().getClass(OFunction.CLASS_NAME), true) + .setMandatory(true).setNotNull(true); + f.createProperty(OScheduledEvent.PROP_STARTTIME, OType.DATETIME, (OType) null, true); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerProxy.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerProxy.java new file mode 100644 index 00000000000..3d37e1dea4a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerProxy.java @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OProxedResource; + +import java.util.Map; + +/** + * Proxy implementation of the Scheduler. + * + * @author Luca Garulli + * @author henryzhao81-at-gmail.com + * @since Mar 28, 2013 + */ +public class OSchedulerProxy extends OProxedResource implements OScheduler { + public OSchedulerProxy(final OScheduler iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + @Override + public void scheduleEvent(final OScheduledEvent scheduler) { + delegate.scheduleEvent(scheduler); + } + + @Override + public void removeEvent(final String eventName) { + delegate.removeEvent(eventName); + } + + @Override + public void updateEvent(final OScheduledEvent event) { + delegate.updateEvent(event); + } + + @Override + public Map getEvents() { + return delegate.getEvents(); + } + + @Override + public OScheduledEvent getEvent(final String name) { + return delegate.getEvent(name); + } + + @Override + public void load() { + delegate.load(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public void create() { + delegate.create(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerTrigger.java b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerTrigger.java new file mode 100755 index 00000000000..c655a3f9df0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/schedule/OSchedulerTrigger.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2012 henryzhao81-at-gmail.com + * + * Licensed 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 com.orientechnologies.orient.core.schedule; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OValidationException; +import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.schedule.OScheduler.STATUS; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Keeps synchronized the scheduled events in memory. + * + * @author Luca Garulli + * @author henryzhao81-at-gmail.com + * @since Mar 28, 2013 + */ + +public class OSchedulerTrigger extends ODocumentHookAbstract implements ORecordHook.Scoped { + + private static final SCOPE[] SCOPES = { SCOPE.CREATE, SCOPE.UPDATE, SCOPE.DELETE }; + + public OSchedulerTrigger(ODatabaseDocument database) { + super(database); + } + + @Override + public SCOPE[] getScopes() { + return SCOPES; + } + + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.BOTH; + } + + @Override + public RESULT onTrigger(TYPE iType, ORecord iRecord) { + OImmutableClass clazz = null; + if (iRecord instanceof ODocument) + clazz = ODocumentInternal.getImmutableSchemaClass((ODocument) iRecord); + if (clazz == null || !clazz.isScheduler()) + return RESULT.RECORD_NOT_CHANGED; + return super.onTrigger(iType, iRecord); + } + + @Override + public RESULT onRecordBeforeCreate(final ODocument iDocument) { + String name = iDocument.field(OScheduledEvent.PROP_NAME); + final OScheduledEvent event = database.getMetadata().getScheduler().getEvent(name); + if (event != null && event.getDocument() != iDocument) { + throw new ODatabaseException("Scheduled event with name '" + name + "' already exists in database"); + } + + iDocument.field(OScheduledEvent.PROP_STATUS, STATUS.STOPPED.name()); + return RESULT.RECORD_CHANGED; + } + + @Override + public void onRecordAfterCreate(final ODocument iDocument) { + database.getMetadata().getScheduler().scheduleEvent(new OScheduledEvent(iDocument)); + } + + @Override + public RESULT onRecordBeforeUpdate(final ODocument iDocument) { + try { + final String schedulerName = iDocument.field(OScheduledEvent.PROP_NAME); + OScheduledEvent event = database.getMetadata().getScheduler().getEvent(schedulerName); + + if (event != null) { + // UPDATED EVENT + final Set dirtyFields = new HashSet(Arrays.asList(iDocument.getDirtyFields())); + + if (dirtyFields.contains(OScheduledEvent.PROP_NAME)) + throw new OValidationException("Scheduled event cannot change name"); + + if (dirtyFields.contains(OScheduledEvent.PROP_RULE)) { + // RULE CHANGED, STOP CURRENT EVENT AND RESCHEDULE IT + database.getMetadata().getScheduler().updateEvent(new OScheduledEvent(iDocument)); + } else { + iDocument.field(OScheduledEvent.PROP_STATUS, STATUS.STOPPED.name()); + event.fromStream(iDocument); + } + + return RESULT.RECORD_CHANGED; + } + + } catch (Exception ex) { + OLogManager.instance().error(this, "Error on updating scheduled event", ex); + } + return RESULT.RECORD_NOT_CHANGED; + } + + @Override + public void onRecordAfterDelete(final ODocument iDocument) { + final String eventName = iDocument.field(OScheduledEvent.PROP_NAME); + database.getMetadata().getScheduler().removeEvent(eventName); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OAuditingOperation.java b/core/src/main/java/com/orientechnologies/orient/core/security/OAuditingOperation.java new file mode 100644 index 00000000000..10fee0f18a9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OAuditingOperation.java @@ -0,0 +1,80 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.db.record.ORecordOperation; + +/** + * Enumerates the available auditing OAuditingOperation types. + * + * @author S. Colin Leister + * + */ +public enum OAuditingOperation { + UNSPECIFIED((byte)-1, "unspecified"), + CREATED(ORecordOperation.CREATED, "created"), + LOADED(ORecordOperation.LOADED, "loaded"), + UPDATED(ORecordOperation.UPDATED, "updated"), + DELETED(ORecordOperation.DELETED, "deleted"), + COMMAND((byte)4, "command"), + CREATEDCLASS((byte)5, "createdClass"), + DROPPEDCLASS((byte)6, "droppedClass"), + CHANGEDCONFIG((byte)7, "changedConfig"), + NODEJOINED((byte)8, "nodeJoined"), + NODELEFT((byte)9, "nodeLeft"), + SECURITY((byte)10, "security"), + RELOADEDSECURITY((byte)11, "reloadedSecurity"); + + private byte byteOp = -1; // -1: unspecified; + private String stringOp = "unspecified"; + + private OAuditingOperation(byte byteOp, String stringOp) { + this.byteOp = byteOp; + this.stringOp = stringOp; + } + + public byte getByte() { + return byteOp; + } + + @Override + public String toString() { + return stringOp; + } + + public static OAuditingOperation getByString(String value) { + if (value == null || value.isEmpty()) return UNSPECIFIED; + + for (OAuditingOperation op : values()) { + if (op.toString().equalsIgnoreCase(value)) return op; + } + + return UNSPECIFIED; + } + + public static OAuditingOperation getByByte(byte value) { + for (OAuditingOperation op : values()) { + if (op.getByte() == value) return op; + } + + return UNSPECIFIED; + } +} + diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OCredentialInterceptor.java b/core/src/main/java/com/orientechnologies/orient/core/security/OCredentialInterceptor.java new file mode 100644 index 00000000000..c3ae3981439 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OCredentialInterceptor.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.exception.OSecurityException; + +/** + * Provides a basic credential interceptor interface. + * + * @author S. Colin Leister + * + */ +public interface OCredentialInterceptor +{ + public String getUsername(); + public String getPassword(); + + public void intercept(final String url, final String username, final String password) throws OSecurityException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/ODefaultCI.java b/core/src/main/java/com/orientechnologies/orient/core/security/ODefaultCI.java new file mode 100644 index 00000000000..3e63c22c97d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/ODefaultCI.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2016 OrientDB LTD + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.security.OCredentialInterceptor; + +/** + * Provides a default credential interceptor that does nothing. + * + * @author S. Colin Leister + * + */ +public class ODefaultCI implements OCredentialInterceptor { + private String username; + private String password; + + public String getUsername() { return this.username; } + public String getPassword() { return this.password; } + + public void intercept(final String url, final String username, final String password) throws OSecurityException { + this.username = username; + this.password = password; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OInvalidPasswordException.java b/core/src/main/java/com/orientechnologies/orient/core/security/OInvalidPasswordException.java new file mode 100644 index 00000000000..5e69b477472 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OInvalidPasswordException.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +//import com.orientechnologies.common.exception.OSecurityException; + +import com.orientechnologies.orient.core.exception.OSecurityException; + +/** + * An exception for invalid passwords. + * + * @author S. Colin Leister + * + */ +@SuppressWarnings("serial") +public class OInvalidPasswordException extends OSecurityException { + + public OInvalidPasswordException(OInvalidPasswordException exception) { + super(exception); + } + + public OInvalidPasswordException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityFactory.java b/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityFactory.java new file mode 100644 index 00000000000..d8292ac39d0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityFactory.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.metadata.security.OSecurity; + +/** + * Provides an interface for creating new OSecurity instances. + * + * @author S. Colin Leister + * + */ +public interface OSecurityFactory { + OSecurity newSecurity(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityManager.java b/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityManager.java new file mode 100755 index 00000000000..6787f094177 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OSecurityManager.java @@ -0,0 +1,340 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.common.collection.OLRUCache; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.metadata.security.OSecurity; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +public class OSecurityManager { + public static final String HASH_ALGORITHM = "SHA-256"; + public static final String HASH_ALGORITHM_PREFIX = "{" + HASH_ALGORITHM + "}"; + + public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + public static final String PBKDF2_ALGORITHM_PREFIX = "{" + PBKDF2_ALGORITHM + "}"; + + public static final String PBKDF2_SHA256_ALGORITHM = "PBKDF2WithHmacSHA256"; + public static final String PBKDF2_SHA256_ALGORITHM_PREFIX = "{" + PBKDF2_SHA256_ALGORITHM + "}"; + + public static final int SALT_SIZE = 24; + public static final int HASH_SIZE = 24; + + private static final OSecurityManager instance = new OSecurityManager(); + private volatile OSecurityFactory securityFactory = new OSecuritySharedFactory(); + + private MessageDigest md; + + private static Map SALT_CACHE = null; + + static { + final int cacheSize = OGlobalConfiguration.SECURITY_USER_PASSWORD_SALT_CACHE_SIZE.getValueAsInteger(); + if (cacheSize > 0) { + SALT_CACHE = Collections.synchronizedMap(new OLRUCache(cacheSize)); + } + } + + public OSecurityManager() { + try { + md = MessageDigest.getInstance(HASH_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + OLogManager.instance().error(this, "Cannot use OSecurityManager", e); + } + } + + public static String createHash(final String iInput, String iAlgorithm) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + if (iAlgorithm == null) + iAlgorithm = HASH_ALGORITHM; + + final MessageDigest msgDigest = MessageDigest.getInstance(iAlgorithm); + + return byteArrayToHexStr(msgDigest.digest(iInput.getBytes("UTF-8"))); + } + + public static OSecurityManager instance() { + return instance; + } + + /** + * Checks if an hash string matches a password, based on the algorithm found on hash string. + * + * @param iHash + * Hash string. Can contain the algorithm as prefix in the format {ALGORITHM}-HASH. + * @param iPassword + * @return + */ + public boolean checkPassword(final String iPassword, final String iHash) { + if (iHash.startsWith(HASH_ALGORITHM_PREFIX)) { + final String s = iHash.substring(HASH_ALGORITHM_PREFIX.length()); + return createSHA256(iPassword).equals(s); + + } else if (iHash.startsWith(PBKDF2_ALGORITHM_PREFIX)) { + final String s = iHash.substring(PBKDF2_ALGORITHM_PREFIX.length()); + return checkPasswordWithSalt(iPassword, s, PBKDF2_ALGORITHM); + + } else if (iHash.startsWith(PBKDF2_SHA256_ALGORITHM_PREFIX)) { + final String s = iHash.substring(PBKDF2_SHA256_ALGORITHM_PREFIX.length()); + return checkPasswordWithSalt(iPassword, s, PBKDF2_SHA256_ALGORITHM); + } + + // Do not compare raw strings against each other, to avoid timing attacks. + // Instead, hash them both with a cryptographic hash function and + // compare their hashes with a constant-time comparison method. + return MessageDigest.isEqual(digestSHA256(iPassword), digestSHA256(iHash)); + } + + public String createSHA256(final String iInput) { + return byteArrayToHexStr(digestSHA256(iInput)); + } + + /** + * Hashes the input string. + * + * @param iInput + * String to hash + * @param iIncludeAlgorithm + * Include the algorithm used or not + * @return + */ + public String createHash(final String iInput, final String iAlgorithm, final boolean iIncludeAlgorithm) { + if (iInput == null) + throw new IllegalArgumentException("Input string is null"); + + if (iAlgorithm == null) + throw new IllegalArgumentException("Algorithm is null"); + + final StringBuilder buffer = new StringBuilder(128); + + final String algorithm = validateAlgorithm(iAlgorithm); + + if (iIncludeAlgorithm) { + buffer.append('{'); + buffer.append(algorithm); + buffer.append('}'); + } + + final String transformed; + if (HASH_ALGORITHM.equalsIgnoreCase(algorithm)) { + transformed = createSHA256(iInput); + } else if (PBKDF2_ALGORITHM.equalsIgnoreCase(algorithm)) { + transformed = createHashWithSalt(iInput, OGlobalConfiguration.SECURITY_USER_PASSWORD_SALT_ITERATIONS.getValueAsInteger(), + algorithm); + } else if (PBKDF2_SHA256_ALGORITHM.equalsIgnoreCase(algorithm)) { + transformed = createHashWithSalt(iInput, OGlobalConfiguration.SECURITY_USER_PASSWORD_SALT_ITERATIONS.getValueAsInteger(), + algorithm); + } else + throw new IllegalArgumentException("Algorithm '" + algorithm + "' is not supported"); + + buffer.append(transformed); + + return buffer.toString(); + } + + public synchronized byte[] digestSHA256(final String iInput) { + if (iInput == null) + return null; + + try { + return md.digest(iInput.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + final String message = "The requested encoding is not supported: cannot execute security checks"; + OLogManager.instance().error(this, message, e); + + throw OException.wrapException(new OConfigurationException(message), e); + } + } + + public String createHashWithSalt(final String iPassword) { + return createHashWithSalt(iPassword, OGlobalConfiguration.SECURITY_USER_PASSWORD_SALT_ITERATIONS.getValueAsInteger(), + OGlobalConfiguration.SECURITY_USER_PASSWORD_DEFAULT_ALGORITHM.getValueAsString()); + } + + public String createHashWithSalt(final String iPassword, final int iIterations, final String algorithm) { + final SecureRandom random = new SecureRandom(); + final byte[] salt = new byte[SALT_SIZE]; + random.nextBytes(salt); + + // Hash the password + final byte[] hash = getPbkdf2(iPassword, salt, iIterations, HASH_SIZE, validateAlgorithm(algorithm)); + + return byteArrayToHexStr(hash) + ":" + byteArrayToHexStr(salt) + ":" + iIterations; + } + + public boolean checkPasswordWithSalt(final String iPassword, final String iHash) { + return checkPasswordWithSalt(iPassword, iHash, + OGlobalConfiguration.SECURITY_USER_PASSWORD_DEFAULT_ALGORITHM.getValueAsString()); + } + + public boolean checkPasswordWithSalt(final String iPassword, final String iHash, final String algorithm) { + + if (!isAlgorithmSupported(algorithm)) { + OLogManager.instance().error(this, "The password hash algorithm is not supported: %s", algorithm); + return false; + } + + // SPLIT PARTS + final String[] params = iHash.split(":"); + if (params.length != 3) + throw new IllegalArgumentException("Hash does not contain the requested parts: ::"); + + final byte[] hash = hexToByteArray(params[0]); + final byte[] salt = hexToByteArray(params[1]); + final int iterations = Integer.parseInt(params[2]); + + final byte[] testHash = getPbkdf2(iPassword, salt, iterations, hash.length, algorithm); + return MessageDigest.isEqual(hash, testHash); + } + + private byte[] getPbkdf2(final String iPassword, final byte[] salt, final int iterations, final int bytes, + final String algorithm) { + String cacheKey = null; + + final String hashedPassword = createSHA256(iPassword + new String(salt)); + + if (SALT_CACHE != null) { + // SEARCH IN CACHE FIRST + cacheKey = hashedPassword + "|" + Arrays.toString(salt) + "|" + iterations + "|" + bytes; + final byte[] encoded = SALT_CACHE.get(cacheKey); + if (encoded != null) + return encoded; + } + + final PBEKeySpec spec = new PBEKeySpec(iPassword.toCharArray(), salt, iterations, bytes * 8); + final SecretKeyFactory skf; + try { + skf = SecretKeyFactory.getInstance(algorithm); + final byte[] encoded = skf.generateSecret(spec).getEncoded(); + + if (SALT_CACHE != null) { + // SAVE IT IN CACHE + SALT_CACHE.put(cacheKey, encoded); + } + + return encoded; + } catch (Exception e) { + throw OException.wrapException(new OSecurityException("Cannot create a key with '" + algorithm + "' algorithm"), e); + } + } + + /** + * Returns true if the algorithm is supported by the current version of Java + */ + private static boolean isAlgorithmSupported(final String algorithm) { + // Java 7 specific checks. + if (Runtime.class.getPackage() != null && Runtime.class.getPackage().getImplementationVersion() != null) { + if (Runtime.class.getPackage().getImplementationVersion().startsWith("1.7")) { + // Java 7 does not support the PBKDF2_SHA256_ALGORITHM. + if (algorithm != null && algorithm.equals(PBKDF2_SHA256_ALGORITHM)) { + return false; + } + } + } + + return true; + } + + private String validateAlgorithm(final String iAlgorithm) { + String validAlgo = iAlgorithm; + + if (!isAlgorithmSupported(iAlgorithm)) { + // Downgrade it to PBKDF2_ALGORITHM. + validAlgo = PBKDF2_ALGORITHM; + + OLogManager.instance().debug(this, "The %s algorithm is not supported, downgrading to %s", iAlgorithm, validAlgo); + } + + return validAlgo; + } + + public static String byteArrayToHexStr(final byte[] data) { + if (data == null) + return null; + + final char[] chars = new char[data.length * 2]; + for (int i = 0; i < data.length; i++) { + final byte current = data[i]; + final int hi = (current & 0xF0) >> 4; + final int lo = current & 0x0F; + chars[2 * i] = (char) (hi < 10 ? ('0' + hi) : ('A' + hi - 10)); + chars[2 * i + 1] = (char) (lo < 10 ? ('0' + lo) : ('A' + lo - 10)); + } + return new String(chars); + } + + private static byte[] hexToByteArray(final String data) { + final byte[] hex = new byte[data.length() / 2]; + for (int i = 0; i < hex.length; i++) + hex[i] = (byte) Integer.parseInt(data.substring(2 * i, 2 * i + 2), 16); + + return hex; + } + + public OCredentialInterceptor newCredentialInterceptor() { + OCredentialInterceptor ci = null; + + try { + String ciClass = OGlobalConfiguration.CLIENT_CREDENTIAL_INTERCEPTOR.getValueAsString(); + + if (ciClass != null) { + Class cls = Class.forName(ciClass); // Throws a ClassNotFoundException if not found. + + if (OCredentialInterceptor.class.isAssignableFrom(cls)) { + ci = (OCredentialInterceptor) cls.newInstance(); + } + } + } catch (Exception ex) { + OLogManager.instance().debug(this, "newCredentialInterceptor() Exception creating CredentialInterceptor", ex); + } + + return ci; + } + + public OSecurityFactory getSecurityFactory() { + return securityFactory; + } + + public void setSecurityFactory(OSecurityFactory factory) { + if (factory != null) + securityFactory = factory; + else + securityFactory = new OSecuritySharedFactory(); + } + + public OSecurity newSecurity() { + if (securityFactory != null) + return securityFactory.newSecurity(); + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySharedFactory.java b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySharedFactory.java new file mode 100644 index 00000000000..9de939fd44c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySharedFactory.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.metadata.security.OSecurity; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; + +/** + * Implements the OSecurityFactory interface for OSecurityShared instances. + * + * @author S. Colin Leister + * + */ +public class OSecuritySharedFactory implements OSecurityFactory +{ + public OSecurity newSecurity() + { + return new OSecurityShared(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystem.java b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystem.java new file mode 100644 index 00000000000..4ef2318973b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystem.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Provides a basic interface for a modular security system. + * + * @author S. Colin Leister + * + */ +public interface OSecuritySystem { + void shutdown(); + + // Some external security implementations may permit falling back to a + // default authentication mode if external authentication fails. + boolean isDefaultAllowed(); + + // Returns the actual username if successful, null otherwise. + // Some token-based authentication (e.g., SPNEGO tokens have the user's name embedded in the service ticket). + String authenticate(final String username, final String password); + + // Used for generating the appropriate HTTP authentication mechanism. The chain of authenticators is used for this. + String getAuthenticationHeader(final String databaseName); + + ODocument getConfig(); + + ODocument getComponentConfig(final String name); + + /** + * Returns the "System User" associated with 'username' from the system database. If not found, returns null. dbName is used to + * filter the assigned roles. It may be null. + */ + OUser getSystemUser(final String username, final String dbName); + + // Walks through the list of Authenticators. + boolean isAuthorized(final String username, final String resource); + + boolean isEnabled(); + + // Indicates if passwords should be stored when creating new users. + boolean arePasswordsStored(); + + // Indicates if the primary security mechanism supports single sign-on. + boolean isSingleSignOnSupported(); + + /** + * Logs to the auditing service, if installed. + * + * @param dbName + * May be null or empty. + * @param username + * May be null or empty. + */ + void log(final OAuditingOperation operation, final String dbName, final String username, final String message); + + void registerSecurityClass(final Class cls); + + void reload(final String cfgPath); + + void reload(final ODocument jsonConfig); + + void reloadComponent(final String name, final ODocument jsonConfig); + + /** + * Called each time one of the security classes (OUser, ORole, OServerRole) is modified. + */ + void securityRecordChange(final String dbURL, final ODocument record); + + void unregisterSecurityClass(final Class cls); + + // If a password validator is registered with the security system, it will be called to validate + // the specified password. An OInvalidPasswordException is thrown if the password does not meet + // the password validator's requirements. + void validatePassword(final String password) throws OInvalidPasswordException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystemException.java b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystemException.java new file mode 100644 index 00000000000..e9b9b49235a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/OSecuritySystemException.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security; + +import com.orientechnologies.common.exception.OException; + +/** + * OSecuritySystem Exception + * + * @author S. Colin Leister + * + */ +@SuppressWarnings("serial") +public class OSecuritySystemException extends OException { + + public OSecuritySystemException(OSecuritySystemException exception) { + super(exception); + } + + public OSecuritySystemException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKerberosCredentialInterceptor.java b/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKerberosCredentialInterceptor.java new file mode 100644 index 00000000000..e73b84acf3e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKerberosCredentialInterceptor.java @@ -0,0 +1,220 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security.kerberos; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.security.OCredentialInterceptor; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import java.net.URISyntaxException; +import java.net.URI; + +import java.security.Principal; +import java.security.PrivilegedAction; + +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.auth.Subject; + +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; + +/** + * Provides a Kerberos credential interceptor. + * + * @author S. Colin Leister + * + */ +public class OKerberosCredentialInterceptor implements OCredentialInterceptor { + private String principal; + private String serviceTicket; + + public String getUsername() { return this.principal; } + public String getPassword() { return this.serviceTicket; } + + public void intercept(final String url, final String principal, final String spn) throws OSecurityException + { + // While the principal can be determined from the ticket cache, if a client keytab is used instead, + // it may contain multiple principals. + if(principal == null || principal.isEmpty()) throw new OSecurityException("OKerberosCredentialInterceptor Principal cannot be null!"); + + this.principal = principal; + + String actualSPN = spn; + + // spn should be the SPN of the service. + if(spn == null || spn.isEmpty()) { + // If spn is null or an empty string, the SPN will be generated from the URL like this: + // OrientDB/host + if(url == null || url.isEmpty()) throw new OSecurityException("OKerberosCredentialInterceptor URL and SPN cannot both be null!"); + + try { + String tempURL = url; + + // Without the // URI can't parse URLs correctly, so we add //. + if(tempURL.startsWith("remote:") && !tempURL.startsWith("remote://")) + tempURL = tempURL.replace("remote:", "remote://"); + + URI remoteURI = new URI(tempURL); + + String host = remoteURI.getHost(); + + if(host == null) throw new OSecurityException("OKerberosCredentialInterceptor Could not create SPN from URL: " + url); + + actualSPN = "OrientDB/" + host; + } catch(URISyntaxException ex) { + throw new OSecurityException("OKerberosCredentialInterceptor Could not create SPN from URL: " + url); + } + } + + // Defaults to the environment variable. + String config = System.getenv("KRB5_CONFIG"); + String ckc = OGlobalConfiguration.CLIENT_KRB5_CONFIG.getValueAsString(); + if(ckc != null) config = ckc; + + // Defaults to the environment variable. + String ccname = System.getenv("KRB5CCNAME"); + String ccn = OGlobalConfiguration.CLIENT_KRB5_CCNAME.getValueAsString(); + if(ccn != null) ccname = ccn; + + // Defaults to the environment variable. + String ktname = System.getenv("KRB5_CLIENT_KTNAME"); + String ckn = OGlobalConfiguration.CLIENT_KRB5_KTNAME.getValueAsString(); + if(ckn != null) ktname = ckn; + + if(config == null) throw new OSecurityException("OKerberosCredentialInterceptor KRB5 Config cannot be null!"); + if(ccname == null && ktname == null) throw new OSecurityException("OKerberosCredentialInterceptor KRB5 Credential Cache and KeyTab cannot both be null!"); + + LoginContext lc = null; + + try { + System.setProperty("java.security.krb5.conf", config); + + OKrb5ClientLoginModuleConfig cfg = new OKrb5ClientLoginModuleConfig(principal, ccname, ktname); + + lc = new LoginContext("ignore", null, null, cfg); + lc.login(); + } catch(LoginException lie) { + OLogManager.instance().debug(this, "intercept() LoginException", lie); + + throw new OSecurityException("OKerberosCredentialInterceptor Client Validation Exception!"); + } + + Subject subject = lc.getSubject(); + + // Assign the client's principal name. + // this.principal = getFirstPrincipal(subject); + + // if(this.principal == null) throw new OSecurityException("OKerberosCredentialInterceptor Cannot obtain client principal!"); + + this.serviceTicket = getServiceTicket(subject, principal, actualSPN); + + try { + lc.logout(); + } catch(LoginException loe) { + OLogManager.instance().debug(this, "intercept() LogoutException", loe); + } + + if(this.serviceTicket == null) throw new OSecurityException("OKerberosCredentialInterceptor Cannot obtain the service ticket!"); + } + + private String getFirstPrincipal(Subject subject) { + if(subject != null) { + final Object[] principals = subject.getPrincipals().toArray(); + final Principal p = (Principal)principals[0]; + + return p.getName(); + } + + return null; + } + + private String getServiceTicket(final Subject subject, final String principal, final String servicePrincipalName) { + try { + GSSManager manager = GSSManager.getInstance(); + GSSName serviceName = manager.createName(servicePrincipalName, GSSName.NT_USER_NAME); + + Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); + + // Initiator. + final GSSContext context = manager.createContext(serviceName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME); + + if(context != null) { + // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html + // When performing operations as a particular Subject, e.g. Subject.doAs(...) or Subject.doAsPrivileged(...), + // the to-be-used GSSCredential should be added to Subject's private credential set. Otherwise, + // the GSS operations will fail since no credential is found. + boolean useNativeJgss = Boolean.getBoolean("sun.security.jgss.native"); + + if(useNativeJgss) { + OLogManager.instance().info(this, "getServiceTicket() Using Native JGSS"); + + try { + GSSName clientName = manager.createName(principal, GSSName.NT_USER_NAME); + + // null: indicates using the default principal. + GSSCredential cred = manager.createCredential(clientName, GSSContext.DEFAULT_LIFETIME, krb5Oid, GSSCredential.INITIATE_ONLY); + + subject.getPrivateCredentials().add(cred); + } catch(GSSException gssEx) { + OLogManager.instance().error(this, "getServiceTicket() Use Native JGSS GSSException", gssEx); + } + } + + // The GSS context initiation has to be performed as a privileged action. + byte [] serviceTicket = Subject.doAs(subject, new PrivilegedAction() { + public byte[] run() { + try { + byte[] token = new byte[0]; + + // This is a one pass context initialisation. + context.requestMutualAuth(false); + context.requestCredDeleg(false); + return context.initSecContext(token, 0, token.length); + } catch(Exception inner) { + OLogManager.instance().debug(this, "getServiceTicket() doAs() Exception", inner); + } + + return null; + } + }); + + if(serviceTicket != null) return OBase64Utils.encodeBytes(serviceTicket); + + context.dispose(); + } else { + OLogManager.instance().debug(this, "getServiceTicket() GSSContext is null!"); + } + } catch(Exception ex) { + OLogManager.instance().error(this, "getServiceTicket() Exception", ex); + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKrb5ClientLoginModuleConfig.java b/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKrb5ClientLoginModuleConfig.java new file mode 100644 index 00000000000..ff9cebb82af --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/kerberos/OKrb5ClientLoginModuleConfig.java @@ -0,0 +1,103 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security.kerberos; + +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; + +/** + * Custom Kerberos client login configuration. + * + * @author S. Colin Leister + * + */ +public class OKrb5ClientLoginModuleConfig extends Configuration +{ + final String LoginModule = "com.sun.security.auth.module.Krb5LoginModule"; + + private final AppConfigurationEntry[] _appConfigEntries = new AppConfigurationEntry[1]; + + public AppConfigurationEntry[] getAppConfigurationEntry(String applicationName) + { + return _appConfigEntries; + } +/* + public OKrb5ClientLoginModuleConfig(String ccPath) + { + if(ccPath != null) + { + final Map options = new HashMap(); + + options.put("useTicketCache", "true"); + options.put("ticketCache", ccPath); + options.put("doNotPrompt", "true"); + + _appConfigEntries[0] = new AppConfigurationEntry(LoginModule, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); + } + } +*/ + + public OKrb5ClientLoginModuleConfig(String principal, String ccPath, String ktPath) + { + this(principal, true, ccPath, ktPath); + } + + public OKrb5ClientLoginModuleConfig(String principal, boolean useTicketCache, String ccPath, String ktPath) + { + final Map options = new HashMap(); + + options.put("principal", principal); + + // This is the default, but let's be specific. + // If isInitiator is true, then acquiring a TGT is mandatory. + // If we're using a valid ticket cache then we should already have a TGT and this is technically not needed. + // If not, and we use the keytab for authentication, then we'll have to acquire a TGT. + options.put("isInitiator", "true"); + + if(ccPath != null && ccPath.length() > 0) + { + if(useTicketCache) + { + options.put("useTicketCache", "true"); + options.put("ticketCache", ccPath); + } + else + { + options.put("useTicketCache", "false"); + } + } + + if(ktPath != null && ktPath.length() > 0) + { + options.put("useKeyTab", "true"); + options.put("keyTab", ktPath); + + // storeKey is essential or else you'll get an "Invalid argument (400) - Cannot find key of appropriate type to decrypt AP REP" in your acceptSecContext() call. + options.put("storeKey", "true"); + } + + options.put("doNotPrompt", "true"); + + _appConfigEntries[0] = new AppConfigurationEntry(LoginModule, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKey.java b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKey.java new file mode 100644 index 00000000000..119d39d5c7a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKey.java @@ -0,0 +1,602 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.security.symmetrickey; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OSystemVariableResolver; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.security.AlgorithmParameters; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.KeySpec; +import java.util.UUID; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Implements a symmetric key utility class that can create default keys and keys from a String, a file, + * a KeyStore, and from the OSymmetricKeyConfig interface. + * + * Static creation methods are provided for each type: + * OSymmetricKey.fromConfig() + * OSymmetricKey.fromString() + * OSymmetricKey.fromFile() + * OSymmetricKey.fromStream() + * OSymmetricKey.fromKeystore() + * + * The encrypt() methods return a specialized Base64-encoded JSON document with these properties (depending on the cipher transform): + * "algorithm", "transform", "iv", "payload" + * + * The decrypt() and decryptAsString() methods accept the Base64-encoded JSON document. + * + * A symmetric key credential interceptor is provided (OSymmetricKeyCI) as well as several authenticators: + * OSecuritySymmetricKeyAuth, OSystemSymmetricKeyAuth + * + * @author S. Colin Leister + */ +public class OSymmetricKey { + // These are just defaults. + private String seedAlgorithm = "PBKDF2WithHmacSHA1"; + private String seedPhrase = UUID.randomUUID().toString(); + // Holds the length of the salt byte array. + private int saltLength = 64; + // Holds the default number of iterations used. This may be overridden in the configuration. + private int iteration = 65536; + private String secretKeyAlgorithm = "AES"; + private String defaultCipherTransformation = "AES/CBC/PKCS5Padding"; + // Holds the size of the key (in bits). + private int keySize = 128; + + private SecretKey secretKey; + + // Getters + public String getDefaultCipherTransform(final String transform) { return defaultCipherTransformation; } + public int getIteration(int iteration) { return iteration; } + public String getKeyAlgorithm(final String algorithm) { return secretKeyAlgorithm; } + public int getKeySize(int bits) { return keySize; } + public int getSaltLength(int length) { return saltLength; } + public String getSeedAlgorithm(final String algorithm) { return seedAlgorithm; } + public String getSeedPhrase(final String phrase) { return seedPhrase; } + + // Setters + public OSymmetricKey setDefaultCipherTransform(final String transform) { defaultCipherTransformation = transform; return this; } + public OSymmetricKey setIteration(int iteration) { this.iteration = iteration; return this; } + public OSymmetricKey setKeyAlgorithm(final String algorithm) { secretKeyAlgorithm = algorithm; return this; } + public OSymmetricKey setKeySize(int bits) { keySize = bits; return this; } + public OSymmetricKey setSaltLength(int length) { saltLength = length; return this; } + public OSymmetricKey setSeedAlgorithm(final String algorithm) { seedAlgorithm = algorithm; return this; } + public OSymmetricKey setSeedPhrase(final String phrase) { seedPhrase = phrase; return this; } + + + public OSymmetricKey() { + create(); + } + + /** + * Creates a key based on the algorithm, transformation, and key size specified. + */ + public OSymmetricKey(final String secretKeyAlgorithm, final String cipherTransform, final int keySize) { + this.secretKeyAlgorithm = secretKeyAlgorithm; + this.defaultCipherTransformation = cipherTransform; + this.keySize = keySize; + + create(); + } + + + /** + * Uses the specified SecretKey as the private key and sets key algorithm from the SecretKey. + */ + public OSymmetricKey(final SecretKey secretKey) throws OSecurityException { + if(secretKey == null) throw new OSecurityException("OSymmetricKey(SecretKey) secretKey is null"); + + this.secretKey = secretKey; + this.secretKeyAlgorithm = secretKey.getAlgorithm(); + } + + /** + * Sets the SecretKey based on the specified algorithm and Base64 key specified. + */ + public OSymmetricKey(final String algorithm, final String base64Key) throws OSecurityException { + this.secretKeyAlgorithm = algorithm; + + try { + final byte[] keyBytes = OSymmetricKey.convertFromBase64(base64Key); + + this.secretKey = new SecretKeySpec(keyBytes, secretKeyAlgorithm); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.OSymmetricKey() Exception: " + ex.getMessage()); + } + } + + protected void create() + { + try { + SecureRandom secureRandom = new SecureRandom(); + byte[] salt = secureRandom.generateSeed(saltLength); + + KeySpec keySpec = new PBEKeySpec(seedPhrase.toCharArray(), salt, iteration, keySize); + + SecretKeyFactory factory = SecretKeyFactory.getInstance(seedAlgorithm); + SecretKey tempKey = factory.generateSecret(keySpec); + + secretKey = new SecretKeySpec(tempKey.getEncoded(), secretKeyAlgorithm); + } + catch(Exception ex) + { + throw new OSecurityException("OSymmetricKey.create() Exception: " + ex); + } + } + + /** + * Returns the secret key algorithm portion of the cipher transformation. + */ + protected static String separateAlgorithm(final String cipherTransform) { + String [] array = cipherTransform.split("/"); + + if(array.length > 1) return array[0]; + + return null; + } + + /** + * Creates an OSymmetricKey from an OSymmetricKeyConfig interface. + */ + public static OSymmetricKey fromConfig(final OSymmetricKeyConfig keyConfig) { + if(keyConfig.usesKeyString()) { + return fromString(keyConfig.getKeyAlgorithm(), keyConfig.getKeyString()); + } + else + if(keyConfig.usesKeyFile()) { + return fromFile(keyConfig.getKeyAlgorithm(), keyConfig.getKeyFile()); + } + else + if(keyConfig.usesKeystore()) { + return fromKeystore(keyConfig.getKeystoreFile(), keyConfig.getKeystorePassword(), keyConfig.getKeystoreKeyAlias(), keyConfig.getKeystoreKeyPassword()); + } + else { + throw new OSecurityException("OSymmetricKey(OSymmetricKeyConfig) Invalid configuration"); + } + } + + + /** + * Creates an OSymmetricKey from a Base64 key. + */ + public static OSymmetricKey fromString(final String algorithm, final String base64Key) { + return new OSymmetricKey(algorithm, base64Key); + } + + /** + * Creates an OSymmetricKey from a file containing a Base64 key. + */ + public static OSymmetricKey fromFile(final String algorithm, final String path) { + String base64Key = null; + + try { + java.io.FileInputStream fis = null; + + try { + fis = new java.io.FileInputStream(OSystemVariableResolver.resolveSystemVariables(path)); + + return fromStream(algorithm, fis); + } + finally { + if(fis != null) fis.close(); + } + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.fromFile() Exception: " + ex.getMessage()); + } + } + + /** + * Creates an OSymmetricKey from an InputStream containing a Base64 key. + */ + public static OSymmetricKey fromStream(final String algorithm, final InputStream is) { + String base64Key = null; + + try { + base64Key = OIOUtils.readStreamAsString(is); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.fromStream() Exception: " + ex.getMessage()); + } + + return new OSymmetricKey(algorithm, base64Key); + } + + /** + * Creates an OSymmetricKey from a Java "JCEKS" KeyStore. + * @param path The location of the KeyStore file. + * @param password The password for the KeyStore. May be null. + * @param keyAlias The alias name of the key to be used from the KeyStore. Required. + * @param keyPassword The password of the key represented by keyAlias. May be null. + */ + public static OSymmetricKey fromKeystore(final String path, final String password, final String keyAlias, final String keyPassword) { + OSymmetricKey sk = null; + + try { + KeyStore ks = KeyStore.getInstance("JCEKS"); // JCEKS is required to hold SecretKey entries. + + java.io.FileInputStream fis = null; + + try { + fis = new java.io.FileInputStream(OSystemVariableResolver.resolveSystemVariables(path)); + + return fromKeystore(fis, password, keyAlias, keyPassword); + } + finally { + if(fis != null) fis.close(); + } + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.fromKeystore() Exception: " + ex.getMessage()); + } + } + + /** + * Creates an OSymmetricKey from a Java "JCEKS" KeyStore. + * @param is The InputStream used to load the KeyStore. + * @param password The password for the KeyStore. May be null. + * @param keyAlias The alias name of the key to be used from the KeyStore. Required. + * @param keyPassword The password of the key represented by keyAlias. May be null. + */ + public static OSymmetricKey fromKeystore(final InputStream is, final String password, final String keyAlias, final String keyPassword) { + OSymmetricKey sk = null; + + try { + KeyStore ks = KeyStore.getInstance("JCEKS"); // JCEKS is required to hold SecretKey entries. + + char [] ksPasswdChars = null; + + if(password != null) ksPasswdChars = password.toCharArray(); + + ks.load(is, ksPasswdChars); // ksPasswdChars may be null. + + + char [] ksKeyPasswdChars = null; + + if(keyPassword != null) ksKeyPasswdChars = keyPassword.toCharArray(); + + KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(ksKeyPasswdChars); // ksKeyPasswdChars may be null. + + KeyStore.SecretKeyEntry skEntry = (KeyStore.SecretKeyEntry)ks.getEntry(keyAlias, protParam); + + if(skEntry == null) throw new OSecurityException("SecretKeyEntry is null for key alias: " + keyAlias); + + SecretKey secretKey = skEntry.getSecretKey(); + + sk = new OSymmetricKey(secretKey); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.fromKeystore() Exception: " + ex.getMessage()); + } + + return sk; + } + + + /** + * Returns the internal SecretKey as a Base64 String. + */ + public String getBase64Key() { + if(secretKey == null) throw new OSecurityException("OSymmetricKey.getBase64Key() SecretKey is null"); + + return convertToBase64(secretKey.getEncoded()); + } + + protected static String convertToBase64(final byte[] bytes) { + String result = null; + + try { + result = OBase64Utils.encodeBytes(bytes); + } catch(Exception ex) { + OLogManager.instance().error(null, "convertToBase64() Exception: %s", ex.getMessage()); + } + + return result; + } + + protected static byte[] convertFromBase64(final String base64) { + byte[] result = null; + + try + { + if(base64 != null) { + result = OBase64Utils.decode(base64.getBytes("UTF8")); + } + } catch(Exception ex) { + OLogManager.instance().error(null, "convertFromBase64() Exception: %s", ex.getMessage()); + } + + return result; + } + + /** + * This is a convenience method that takes a String argument, encodes it as Base64, then calls encrypt(byte[]). + * + * @param value The String to be encoded to Base64 then encrypted. + * + * @return A Base64-encoded JSON document. + */ + public String encrypt(final String value) { + try { + return encrypt(value.getBytes("UTF8")); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.encrypt() Exception: " + ex.getMessage()); + } + } + + /** + * This is a convenience method that takes a String argument, encodes it as Base64, then calls encrypt(byte[]). + * + * @param transform The cipher transformation to use. + * @param value The String to be encoded to Base64 then encrypted. + * + * @return A Base64-encoded JSON document. + */ + public String encrypt(final String transform, final String value) { + try { + return encrypt(transform, value.getBytes("UTF8")); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.encrypt() Exception: " + ex.getMessage()); + } + } + + /** + * This method encrypts an array of bytes. + * + * @param bytes The array of bytes to be encrypted. + * + * @return The encrypted bytes as a Base64-encoded JSON document or null if unsuccessful. + */ + public String encrypt(final byte[] bytes) { + return encrypt(defaultCipherTransformation, bytes); + } + + /** + * This method encrypts an array of bytes. + * + * @param transform The cipher transformation to use. + * @param bytes The array of bytes to be encrypted. + * + * @return The encrypted bytes as a Base64-encoded JSON document or null if unsuccessful. + */ + public String encrypt(final String transform, final byte[] bytes) { + String encodedJSON = null; + + if(secretKey == null) throw new OSecurityException("OSymmetricKey.encrypt() SecretKey is null"); + if(transform == null) throw new OSecurityException("OSymmetricKey.encrypt() Cannot determine cipher transformation"); + + try { + // Throws NoSuchAlgorithmException and NoSuchPaddingException. + Cipher cipher = Cipher.getInstance(transform); + + // If the cipher transformation requires an initialization vector then init() will create a random one. + // (Use cipher.getIV() to retrieve the IV, if it exists.) + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + // If the cipher does not use an IV, this will be null. + byte[] initVector = cipher.getIV(); + +// byte[] initVector = encCipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); + + byte[] encrypted = cipher.doFinal(bytes); + + encodedJSON = encodeJSON(encrypted, initVector); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.encrypt() Exception: " + ex.getMessage()); + } + + return encodedJSON; + } + + protected String encodeJSON(final byte[] encrypted, final byte[] initVector) { + String encodedJSON = null; + + String encryptedBase64 = convertToBase64(encrypted); + String initVectorBase64 = null; + + if(initVector != null) initVectorBase64 = convertToBase64(initVector); + + // Create the JSON document. + StringBuffer sb = new StringBuffer(); + sb.append("{"); + sb.append("\"algorithm\":\""); + sb.append(secretKeyAlgorithm); + sb.append("\",\"transform\":\""); + sb.append(defaultCipherTransformation); + sb.append("\",\"payload\":\""); + sb.append(encryptedBase64); + sb.append("\""); + + if(initVectorBase64 != null) { + sb.append(",\"iv\":\""); + sb.append(initVectorBase64); + sb.append("\""); + } + + sb.append("}"); + + try { + // Convert the JSON document to Base64, for a touch more obfuscation. + encodedJSON = convertToBase64(sb.toString().getBytes("UTF8")); + + } catch(Exception ex) { + + } + + return encodedJSON; + } + + /** + * This method decrypts the Base64-encoded JSON document using the specified algorithm and cipher transformation. + * + * @param encodedJSON The Base64-encoded JSON document. + * + * @return The decrypted array of bytes as a UTF8 String or null if not successful. + * + */ + public String decryptAsString(final String encodedJSON) { + try { + byte[] decrypted = decrypt(encodedJSON); + return new String(decrypted, "UTF8"); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.decryptAsString() Exception: " + ex.getMessage()); + } + } + + /** + * This method decrypts the Base64-encoded JSON document using the specified algorithm and cipher transformation. + * + * @param encodedJSON The Base64-encoded JSON document. + * + * @return The decrypted array of bytes or null if unsuccessful. + * + */ + public byte[] decrypt(final String encodedJSON) { + byte[] result = null; + + if(encodedJSON == null) throw new OSecurityException("OSymmetricKey.decrypt(String) encodedJSON is null"); + + try { + byte[] decoded = convertFromBase64(encodedJSON); + + if(decoded == null) throw new OSecurityException("OSymmetricKey.decrypt(String) encodedJSON could not be decoded"); + + String json = new String(decoded, "UTF8"); + + // Convert the JSON content to an ODocument to make parsing it easier. + final ODocument doc = new ODocument().fromJSON(json, "noMap"); + + // Set a default in case the JSON document does not contain an "algorithm" property. + String algorithm = secretKeyAlgorithm; + + if(doc.containsField("algorithm")) algorithm = doc.field("algorithm"); + + // Set a default in case the JSON document does not contain a "transform" property. + String transform = defaultCipherTransformation; + + if(doc.containsField("transform")) transform = doc.field("transform"); + + String payloadBase64 = doc.field("payload"); + String ivBase64 = doc.field("iv"); + + byte[] payload = null; + byte[] iv = null; + + if(payloadBase64 != null) payload = convertFromBase64(payloadBase64); + if(ivBase64 != null) iv = convertFromBase64(ivBase64); + + // Throws NoSuchAlgorithmException and NoSuchPaddingException. + Cipher cipher = Cipher.getInstance(transform); + + if(iv != null) + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + else + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + result = cipher.doFinal(payload); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.decrypt(String) Exception: " + ex.getMessage()); + } + + return result; + } + + /** + * Saves the internal SecretKey to the specified OutputStream as a Base64 String. + */ + public void saveToStream(final OutputStream os) { + if(os == null) throw new OSecurityException("OSymmetricKey.saveToStream() OutputStream is null"); + + try { + final OutputStreamWriter osw = new OutputStreamWriter(os); + try { + final BufferedWriter writer = new BufferedWriter(osw); + try { + writer.write(getBase64Key()); + } finally { + writer.close(); + } + } finally { + os.close(); + } + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.saveToStream() Exception: " + ex.getMessage()); + } + } + + /** + * Saves the internal SecretKey as a KeyStore. + */ + public void saveToKeystore(final OutputStream os, final String ksPasswd, final String keyAlias, final String keyPasswd) { + if(os == null) throw new OSecurityException("OSymmetricKey.saveToKeystore() OutputStream is null"); + if(ksPasswd == null) throw new OSecurityException("OSymmetricKey.saveToKeystore() Keystore Password is required"); + if(keyAlias == null) throw new OSecurityException("OSymmetricKey.saveToKeystore() Key Alias is required"); + if(keyPasswd == null) throw new OSecurityException("OSymmetricKey.saveToKeystore() Key Password is required"); + + try { + KeyStore ks = KeyStore.getInstance("JCEKS"); + + char [] ksPasswdCA = ksPasswd.toCharArray(); + char [] keyPasswdCA = keyPasswd.toCharArray(); + + // Create a new KeyStore by passing null. + ks.load(null, ksPasswdCA); + + KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(keyPasswdCA); + + KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(secretKey); + ks.setEntry(keyAlias, skEntry, protParam); + + // Save the KeyStore + ks.store(os, ksPasswdCA); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKey.saveToKeystore() Exception: " + ex.getMessage()); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyCI.java b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyCI.java new file mode 100644 index 00000000000..e10c9fd4dcb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyCI.java @@ -0,0 +1,144 @@ +/* + * + * * Copyright 2016 OrientDB LTD + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.security.symmetrickey; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.OCredentialInterceptor; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import java.net.URISyntaxException; +import java.net.URI; + +import java.security.Principal; +import java.security.PrivilegedAction; + +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.auth.Subject; + +/** + * Provides a symmetric key credential interceptor. + * + * The "password" parameter should be a JSON document specifying "keyAlgorithm" and + * "key", "keyFile", or "keyStore". + * + * The method getPassword() will return a Base64-encoded JSON document with the encrypted + * "username" as its payload. + * + * @author S. Colin Leister + * + */ +public class OSymmetricKeyCI implements OCredentialInterceptor { + private String username; + private String encodedJSON = ""; + + public String getUsername() { return this.username; } + public String getPassword() { return this.encodedJSON; } + + /** + * The usual password field should be a JSON representation. + */ + public void intercept(final String url, final String username, final String password) throws OSecurityException + { + if(username == null || username.isEmpty()) throw new OSecurityException("OSymmetricKeyCI username is not valid!"); + if(password == null || password.isEmpty()) throw new OSecurityException("OSymmetricKeyCI password is not valid!"); + + this.username = username; + + // These are all used as defaults if the JSON document is missing any fields. + + // Defaults to "AES". + String algorithm = OGlobalConfiguration.CLIENT_CI_KEYALGORITHM.getValueAsString(); + // Defaults to "AES/CBC/PKCS5Padding". + String transform = OGlobalConfiguration.CLIENT_CI_CIPHERTRANSFORM.getValueAsString(); + String keystoreFile = OGlobalConfiguration.CLIENT_CI_KEYSTORE_FILE.getValueAsString(); + String keystorePassword = OGlobalConfiguration.CLIENT_CI_KEYSTORE_PASSWORD.getValueAsString(); + + ODocument jsonDoc = null; + + try { + jsonDoc = new ODocument().fromJSON(password, "noMap"); + } catch(Exception ex) { + throw new OSecurityException("OSymmetricKeyCI.intercept() Exception: " + ex.getMessage()); + } + + // Override algorithm and transform, if they exist in the JSON document. + if(jsonDoc.containsField("algorithm")) algorithm = jsonDoc.field("algorithm"); + if(jsonDoc.containsField("transform")) transform = jsonDoc.field("transform"); + + // Just in case the default configuration gets changed, check it. + if(transform == null || transform.isEmpty()) throw new OSecurityException("OSymmetricKeyCI.intercept() cipher transformation is required"); + + // If the algorithm is not set, either as a default in the global configuration or in the JSON document, + // then determine the algorithm from the cipher transformation. + if(algorithm == null) algorithm = OSymmetricKey.separateAlgorithm(transform); + + OSymmetricKey key = null; + + // "key" has priority over "keyFile" and "keyStore". + if(jsonDoc.containsField("key")) { + final String base64Key = jsonDoc.field("key"); + + key = OSymmetricKey.fromString(algorithm, base64Key); + key.setDefaultCipherTransform(transform); + } + else // "keyFile" has priority over "keyStore". + if(jsonDoc.containsField("keyFile")) { + key = OSymmetricKey.fromFile(algorithm, (String)jsonDoc.field("keyFile")); + key.setDefaultCipherTransform(transform); + } + else + if(jsonDoc.containsField("keyStore")) { + ODocument ksDoc = jsonDoc.field("keyStore"); + + if(ksDoc.containsField("file")) keystoreFile = ksDoc.field("file"); + + if(keystoreFile == null || keystoreFile.isEmpty()) throw new OSecurityException("OSymmetricKeyCI.intercept() keystore file is required"); + + // Specific to Keystore, but override if present in the JSON document. + if(ksDoc.containsField("password")) keystorePassword = ksDoc.field("password"); + + String keyAlias = ksDoc.field("keyAlias"); + + if(keyAlias == null || keyAlias.isEmpty()) throw new OSecurityException("OSymmetricKeyCI.intercept() keystore key alias is required"); + + // keyPassword may be null. + String keyPassword = ksDoc.field("keyPassword"); + + // keystorePassword may be null. + key = OSymmetricKey.fromKeystore(keystoreFile, keystorePassword, keyAlias, keyPassword); + key.setDefaultCipherTransform(transform); + } + else { + throw new OSecurityException("OSymmetricKeyCI.intercept() No suitable symmetric key property exists"); + } + + // This should never happen, but... + if(key == null) throw new OSecurityException("OSymmetricKeyCI.intercept() OSymmetricKey is null"); + + encodedJSON = key.encrypt(transform, username); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyConfig.java b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyConfig.java new file mode 100644 index 00000000000..37e0ee19315 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeyConfig.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2016 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.security.symmetrickey; + +public interface OSymmetricKeyConfig { + String getKeyString(); + String getKeyFile(); + String getKeyAlgorithm(); + String getKeystoreFile(); + String getKeystorePassword(); + String getKeystoreKeyAlias(); + String getKeystoreKeyPassword(); + + boolean usesKeyString(); + boolean usesKeyFile(); + boolean usesKeystore(); +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeySecurity.java b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeySecurity.java new file mode 100644 index 00000000000..42be9b1c8e2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OSymmetricKeySecurity.java @@ -0,0 +1,226 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.security.symmetrickey; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.OProxedResource; +import com.orientechnologies.orient.core.exception.OSecurityAccessException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.metadata.security.ORestrictedOperation; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OSecurity; +import com.orientechnologies.orient.core.metadata.security.OSecurityRole; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.metadata.security.OToken; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.security.OSecurityManager; +import com.orientechnologies.orient.core.security.symmetrickey.OSymmetricKey; +import com.orientechnologies.orient.core.security.symmetrickey.OUserSymmetricKeyConfig; + +import java.util.List; +import java.util.Set; + +/** + * Provides a symmetric key specific authentication. + * Implements an OSecurity interface that delegates to the specified OSecurity object. + * + * This is used with embedded (non-server) databases, like so: + * db.setProperty(ODatabase.OPTIONS.SECURITY.toString(), OSymmetricKeySecurity.class); + * + * @author S. Colin Leister + * + */ +public class OSymmetricKeySecurity extends OProxedResource implements OSecurity { + public OSymmetricKeySecurity(final OSecurity iDelegate, final ODatabaseDocumentInternal iDatabase) { + super(iDelegate, iDatabase); + } + + public OUser authenticate(final String username, final String password) { + if(delegate == null) throw new OSecurityAccessException("OSymmetricKeySecurity.authenticate() Delegate is null for username: " + username); + + if(database == null) throw new OSecurityAccessException("OSymmetricKeySecurity.authenticate() Database is null for username: " + username); + + final String dbName = database.getName(); + + OUser user = delegate.getUser(username); + + if(user == null) throw new OSecurityAccessException(dbName, "OSymmetricKeySecurity.authenticate() Username or Key is invalid for username: " + username); + + if(user.getAccountStatus() != OSecurityUser.STATUSES.ACTIVE) + throw new OSecurityAccessException(dbName, "OSymmetricKeySecurity.authenticate() User '" + username + "' is not active"); + + try { + OUserSymmetricKeyConfig userConfig = new OUserSymmetricKeyConfig(user); + + OSymmetricKey sk = OSymmetricKey.fromConfig(userConfig); + + String decryptedUsername = sk.decryptAsString(password); + + if(OSecurityManager.instance().checkPassword(username, decryptedUsername)) + return user; + } catch (Exception ex) { + throw new OSecurityAccessException(dbName, "OSymmetricKeySecurity.authenticate() Exception for database: " + dbName + ", username: " + username + " " + ex.getMessage()); + } + + throw new OSecurityAccessException(dbName, "OSymmetricKeySecurity.authenticate() Username or Key is invalid for database: " + dbName + ", username: " + username); + } + + @Override + public boolean isAllowed(final Set iAllowAll, final Set iAllowOperation) { + return delegate.isAllowed(iAllowAll, iAllowOperation); + } + + @Override + public OIdentifiable allowUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return delegate.allowUser(iDocument, iOperationType, iUserName); + } + + @Override + public OIdentifiable allowRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return delegate.allowRole(iDocument, iOperationType, iRoleName); + } + + @Override + public OIdentifiable denyUser(ODocument iDocument, ORestrictedOperation iOperationType, String iUserName) { + return delegate.denyUser(iDocument, iOperationType, iUserName); + } + + @Override + public OIdentifiable denyRole(ODocument iDocument, ORestrictedOperation iOperationType, String iRoleName) { + return delegate.denyRole(iDocument, iOperationType, iRoleName); + } + + public OIdentifiable allowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + return delegate.allowUser(iDocument, iAllowFieldName, iUserName); + } + + public OIdentifiable allowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + return delegate.allowRole(iDocument, iAllowFieldName, iRoleName); + } + + @Override + public OIdentifiable allowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return delegate.allowIdentity(iDocument, iAllowFieldName, iId); + } + + public OIdentifiable disallowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) { + return delegate.disallowUser(iDocument, iAllowFieldName, iUserName); + } + + public OIdentifiable disallowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) { + return delegate.disallowRole(iDocument, iAllowFieldName, iRoleName); + } + + @Override + public OIdentifiable disallowIdentity(ODocument iDocument, String iAllowFieldName, OIdentifiable iId) { + return delegate.disallowIdentity(iDocument, iAllowFieldName, iId); + } + + public OUser create() { + return delegate.create(); + } + + public void load() { + delegate.load(); + } + + public void close(boolean onDelete) { + if (delegate != null) + delegate.close(false); + } + + public OUser authenticate(final OToken authToken) { + return null; + } + + public OUser getUser(final String iUserName) { + return delegate.getUser(iUserName); + } + + public OUser getUser(final ORID iUserId) { + return delegate.getUser(iUserId); + } + + public OUser createUser(final String iUserName, final String iUserPassword, final String... iRoles) { + return delegate.createUser(iUserName, iUserPassword, iRoles); + } + + public OUser createUser(final String iUserName, final String iUserPassword, final ORole... iRoles) { + return delegate.createUser(iUserName, iUserPassword, iRoles); + } + + public ORole getRole(final String iRoleName) { + return delegate.getRole(iRoleName); + } + + public ORole getRole(final OIdentifiable iRole) { + return delegate.getRole(iRole); + } + + public ORole createRole(final String iRoleName, final OSecurityRole.ALLOW_MODES iAllowMode) { + return delegate.createRole(iRoleName, iAllowMode); + } + + public ORole createRole(final String iRoleName, final ORole iParent, final OSecurityRole.ALLOW_MODES iAllowMode) { + return delegate.createRole(iRoleName, iParent, iAllowMode); + } + + public List getAllUsers() { + return delegate.getAllUsers(); + } + + public List getAllRoles() { + return delegate.getAllRoles(); + } + + public String toString() { + return delegate.toString(); + } + + public boolean dropUser(final String iUserName) { + return delegate.dropUser(iUserName); + } + + public boolean dropRole(final String iRoleName) { + return delegate.dropRole(iRoleName); + } + + public void createClassTrigger() { + delegate.createClassTrigger(); + } + + @Override + public OSecurity getUnderlying() { + return delegate; + } + + @Override + public long getVersion() { + return delegate.getVersion(); + } + + @Override + public void incrementVersion() { + delegate.incrementVersion(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OUserSymmetricKeyConfig.java b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OUserSymmetricKeyConfig.java new file mode 100644 index 00000000000..912e56d9c80 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/security/symmetrickey/OUserSymmetricKeyConfig.java @@ -0,0 +1,112 @@ +/* + * + * * Copyright 2016 OrientDB LTD + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.security.symmetrickey; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.security.OUser; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.security.symmetrickey.OSymmetricKeyConfig; +import com.orientechnologies.orient.core.exception.OSecurityException; + +import java.util.Map; + +/** + * Implements the OSymmetricKeyConfig interface for OUser records. + * The constructor looks for a "properties" field on the OUser document. + * The "properties" field should be a JSON document containing the OSymmetricKey-specific fields. + * + * @author S. Colin Leister + * + */ +public class OUserSymmetricKeyConfig implements OSymmetricKeyConfig { + private String keyString; + private String keyFile; + private String keyAlgorithm; + private String keystoreFile; + private String keystorePassword; + private String keystoreKeyAlias; + private String keystoreKeyPassword; + + // OSymmetricKeyConfig + public String getKeyString() { return keyString; } + public String getKeyFile() { return keyFile; } + public String getKeyAlgorithm() { return keyAlgorithm; } + public String getKeystoreFile() { return keystoreFile; } + public String getKeystorePassword() { return keystorePassword; } + public String getKeystoreKeyAlias() { return keystoreKeyAlias; } + public String getKeystoreKeyPassword() { return keystoreKeyPassword; } + // OSymmetricKeyConfig + public boolean usesKeyString() { return keyString != null && !keyString.isEmpty() && keyAlgorithm != null && !keyAlgorithm.isEmpty(); } + public boolean usesKeyFile() { return keyFile != null && !keyFile.isEmpty() && keyAlgorithm != null && !keyAlgorithm.isEmpty(); } + public boolean usesKeystore() { return keystoreFile != null && !keystoreFile.isEmpty() && keystoreKeyAlias != null && !keystoreKeyAlias.isEmpty(); } + ////////// + + public OUserSymmetricKeyConfig(final OUser user) { + if(user == null) throw new OSecurityException("OUserSymmetricKeyConfig() OUser is null"); + + OIdentifiable id = user.getIdentity(); + + if(!(id instanceof ODocument)) throw new OSecurityException("OUserSymmetricKeyConfig() Identity is not an ODocument"); + + ODocument doc = (ODocument)id; + + ODocument props = doc.field("properties"); + + if(props == null) throw new OSecurityException("OUserSymmetricKeyConfig() OUser properties is null"); + + this.keyString = props.field("key"); + + // "keyString" has priority over "keyFile" and "keystore". + if(this.keyString != null) { + // If "key" is used, "keyAlgorithm" is also required. + this.keyAlgorithm = props.field("keyAlgorithm"); + + if(this.keyAlgorithm == null) throw new OSecurityException("OUserSymmetricKeyConfig() keyAlgorithm is required with key"); + } + else { + this.keyFile = props.field("keyFile"); + + // "keyFile" has priority over "keyStore". + + if(this.keyFile != null) { + // If "keyFile" is used, "keyAlgorithm" is also required. + this.keyAlgorithm = props.field("keyAlgorithm"); + + if(this.keyAlgorithm == null) throw new OSecurityException("OUserSymmetricKeyConfig() keyAlgorithm is required with keyFile"); + } + else { + Map ksMap = props.field("keyStore"); + + ODocument ksDoc = new ODocument().fromMap(ksMap); + + if(ksDoc == null) throw new OSecurityException("OUserSymmetricKeyConfig() key, keyFile, and keyStore cannot all be null"); + + this.keystoreFile = ksDoc.field("file"); + this.keystorePassword = ksDoc.field("passsword"); + this.keystoreKeyAlias = ksDoc.field("keyAlias"); + this.keystoreKeyPassword = ksDoc.field("keyPassword"); + + if(this.keystoreFile == null) throw new OSecurityException("OUserSymmetricKeyConfig() keyStore.file is required"); + if(this.keystoreKeyAlias == null) throw new OSecurityException("OUserSymmetricKeyConfig() keyStore.keyAlias is required"); + } + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OBase64Utils.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OBase64Utils.java new file mode 100755 index 00000000000..3da35f4ab9a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OBase64Utils.java @@ -0,0 +1,2036 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization; + +import com.orientechnologies.common.io.OIOException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCommonConst; + +/** + *

          + * Encodes and decodes to and from Base64 notation. + *

          + *

          + * Homepage: http://iharder.net/base64. + *

          + * + *

          + * Example: + *

          + * + * String encoded = Base64.encode( myByteArray );
          + * byte[] myByteArray = Base64.decode( encoded ); + * + *

          + * The options parameter, which appears in a few places, is used to pass several pieces of information to the encoder. In + * the "higher level" methods such as encodeBytes( bytes, options ) the options parameter can be used to indicate such things as + * first gzipping the bytes before encoding them, not inserting linefeeds, and encoding using the URL-safe and Ordered dialects. + *

          + * + *

          + * Note, according to RFC3548, Section 2.1, implementations should not add line + * feeds unless explicitly told to do so. I've got Base64 set to this behavior now, although earlier versions broke lines by + * default. + *

          + * + *

          + * The constants defined in Base64 can be OR-ed together to combine options, so you might make a call like this: + *

          + * + * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); + *

          + * to compress the data before encoding it and then making the output have newline characters. + *

          + *

          + * Also... + *

          + * String encoded = Base64.encodeBytes( crazyString.getBytes() ); + * + * + * + *

          + * Change Log: + *

          + *
            + *
          • v2.3.7 - Fixed subtle bug when base 64 input stream contained the value 01111111, which is an invalid base 64 character but + * should not throw an ArrayIndexOutOfBoundsException either. Led to discovery of mishandling (or potential for better handling) of + * other bad input characters. You should now get an IOException if you try decoding something that has bad characters in it.
          • + *
          • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded string ended in the last column; the buffer was not + * properly shrunk and contained an extra (null) byte that made it into the string.
          • + *
          • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size was wrong for files of size 31, 34, and 37 bytes. + *
          • + *
          • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the Base64.OutputStream closed the Base64 encoding (by + * padding with equals signs) too soon. Also added an option to suppress the automatic decoding of gzipped streams. Also added + * experimental support for specifying a class loader when using the + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} method.
          • + *
          • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java footprint with its CharEncoders and so + * forth. Fixed some javadocs that were inconsistent. Removed imports and specified things like java.io.IOException explicitly + * inline.
          • + *
          • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the final encoded data will be so that the code + * does not have to create two output arrays: an oversized initial one and then a final, exact-sized one. Big win when using the + * {@link #encodeBytesToBytes(byte[])} family of methods (and not using the gzip options which uses a different mechanism with + * streams and stuff).
          • + *
          • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some similar helper methods to be more efficient with + * memory by not returning a String but just a byte array.
          • + *
          • v2.3 - This is not a drop-in replacement! This is two years of comments and bug fixes queued up and finally + * executed. Thanks to everyone who sent me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. Much bad + * coding was cleaned up including throwing exceptions where necessary instead of returning null values or something similar. Here + * are some changes that may affect you: + *
              + *
            • Does not break lines, by default. This is to keep in compliance with + * RFC3548.
            • + *
            • Throws exceptions instead of returning null values. Because some operations (especially those that may permit the + * GZIP option) use IO streams, there is a possiblity of an java.io.IOException being thrown. After some discussion and thought, + * I've changed the behavior of the methods to throw java.io.IOExceptions rather than return null if ever there's an error. I think + * this is more appropriate, though it will require some changes to your code. Sorry, it should have been done this way to begin + * with.
            • + *
            • Removed all references to System.out, System.err, and the like. Shame on me. All I can say is sorry they were ever + * there.
            • + *
            • Throws NullPointerExceptions and IllegalArgumentExceptions as needed such as when passed arrays are null or offsets + * are invalid.
            • + *
            • Cleaned up as much javadoc as I could to avoid any javadoc warnings. This was especially annoying before for people who were + * thorough in their own projects and then had gobs of javadoc warnings on this file.
            • + *
            + *
          • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when using very small files (~< 40 bytes).
          • + *
          • v2.2 - Added some helper methods for encoding/decoding directly from one file to the next. Also added a main() method to + * support command line encoding/decoding from one file to the next. Also added these Base64 dialects: + *
              + *
            1. The default is RFC3548 format.
            2. + *
            3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates URL and file name friendly format as described in + * Section 4 of RFC3548. http://www.faqs.org/rfcs/rfc3548.html
            4. + *
            5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates URL and file name friendly format that preserves + * lexical ordering as described in http://www.faqs.org/qa/rfcc-1940.html
            6. + *
            + * Special thanks to Jim Kellerman at http://www.powerset.com/ for contributing the new + * Base64 dialects.
          • + * + *
          • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading and writing + * to and from files.
          • + *
          • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like EBCDIC).
          • + *
          • v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.
          • + *
          • v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner. The code now + * detects when data that's being decoded is gzip-compressed and will decompress it automatically. Generally things are cleaner. + * You'll probably have to change some method calls that you were making to support the new options format (ints that you + * "OR" together).
          • + *
          • v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
          • + *
          • v1.5 - Output stream pases on flush() command but does not do anything itself. This helps when using GZIP streams. Added the + * ability to GZip-compress objects before encoding them.
          • + *
          • v1.4 - Added helper methods to read/write files.
          • + *
          • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
          • + *
          • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if not completely + * full, was not returned.
          • + *
          • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
          • + *
          • v1.3.3 - Fixed I/O streams which were totally messed up.
          • + *
          + * + *

          + * I am placing this code in the Public Domain. Do with it as you will. This software comes with no guarantees or warranties but + * with plenty of well-wishing instead! Please visit http://iharder.net/base64 periodically + * to check for updates or to contribute improvements. + *

          + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class OBase64Utils { + + /* ******** P U B L I C F I E L D S ******** */ + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** Specify that gzipped data should not be automatically gunzipped. */ + public final static int DONT_GUNZIP = 4; + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. It is important to note that data + * encoded this way is not officially valid Base64, or at the very least should not be called Base64 without also + * specifying that is was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + /* ******** P R I V A T E F I E L D S ******** */ + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte) '\n'; + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in + // encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in + // encoding + + /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. Notice that the last two bytes + * become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, and it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', + (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', + (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', + (byte) 'Z', (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', + (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', + (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + /** + * A {@link OBase64Utils.InputStream} will read data from another java.io.InputStream, given in the constructor, and + * encode/decode to/from Base64 notation on the fly. + * + * @see OBase64Utils + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link OBase64Utils.InputStream} in DECODE mode. + * + * @param in + * the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream(java.io.InputStream in) { + this(in, DECODE); + } // end constructor + + /** + * Constructs a {@link OBase64Utils.InputStream} in either ENCODE or DECODE mode. + *

          + * Valid options: + * + *

          +     *   ENCODE or DECODE: Encode or Decode as data is read.
          +     *   DO_BREAK_LINES: break lines at 76 characters
          +     *     (only meaningful when encoding)
          +     * 
          + *

          + * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in + * the java.io.InputStream from which to read data. + * @param options + * Specified options + * @see OBase64Utils#ENCODE + * @see OBase64Utils#DECODE + * @see OBase64Utils#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream(java.io.InputStream in, int options) { + + super(in); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[bufferLength]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if (position < 0) { + if (encode) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for (int i = 0; i < 3; i++) { + int b = in.read(); + + // If end of stream, b is -1. + if (b >= 0) { + b3[i] = (byte) b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if (numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for (i = 0; i < 4; i++) { + // Read four "meaningful" bytes: + int b = 0; + do { + b = in.read(); + } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); + + if (b < 0) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte) b; + } // end for: each needed input byte + + if (i == 4) { + numSigBytes = decode4to3(b4, 0, buffer, 0, options); + position = 0; + } // end if: got four characters + else if (i == 0) { + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException("Improperly padded Base64 input."); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if (position >= 0) { + // End of relevant data? + if ( /* !encode && */position >= numSigBytes) { + return -1; + } // end if: got data + + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[position++]; + + if (position >= bufferLength) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException("Error in Base64 code reading stream."); + } // end else + } // end read + + /** + * Calls {@link #read()} repeatedly until the end of stream is reached or len bytes are read. Returns number of bytes + * read into array or -1 if end of stream is encountered. + * + * @param dest + * array to hold values + * @param off + * offset for array + * @param len + * max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read(byte[] dest, int off, int len) throws java.io.IOException { + int i; + int b; + for (i = 0; i < len; i++) { + b = read(); + + if (b >= 0) { + dest[off + i] = (byte) b; + } else if (i == 0) { + return -1; + } else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + /** + * A {@link OBase64Utils.OutputStream} will write data to another java.io.OutputStream, given in the constructor, and + * encode/decode to/from Base64 notation on the fly. + * + * @see OBase64Utils + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link OBase64Utils.OutputStream} in ENCODE mode. + * + * @param out + * the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); + } // end constructor + + /** + * Constructs a {@link OBase64Utils.OutputStream} in either ENCODE or DECODE mode. + *

          + * Valid options: + * + *

          +     *   ENCODE or DECODE: Encode or Decode as data is read.
          +     *   DO_BREAK_LINES: don't break lines at 76 characters
          +     *     (only meaningful when encoding)
          +     * 
          + *

          + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out + * the java.io.OutputStream to which data will be written. + * @param options + * Specified options. + * @see OBase64Utils#ENCODE + * @see OBase64Utils#DECODE + * @see OBase64Utils#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[bufferLength]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are buffered three at a + * time before the output stream actually gets a write() call. When decoding, bytes are buffered four at a time. + * + * @param theByte + * the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theByte); + return; + } // end if: supsended + + // Encode? + if (encode) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to encode. + + this.out.write(encode3to4(b4, buffer, bufferLength, options)); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + this.out.write(NEW_LINE); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to output. + + int len = OBase64Utils.decode4to3(buffer, 0, b4, 0, options); + out.write(b4, 0, len); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); + } // end else: not white space either + } // end else: decoding + } // end write + + /** + * Calls {@link #write(int)} repeatedly until len bytes are written. + * + * @param theBytes + * array from which to read bytes + * @param off + * offset for array + * @param len + * max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write(byte[] theBytes, int off, int len) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theBytes, off, len); + return; + } // end if: supsended + + for (int i = 0; i < len; i++) { + write(theBytes[off + i]); + } // end for: each byte written + + } // end write + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream. + * + * @throws java.io.IOException + * if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position, options)); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException("Base64 input not properly padded."); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + /** + * Suspends encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream. + * + * @throws java.io.IOException + * if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + /** + * Resumes encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + } // end inner class OutputStream + + /** Defeats instantiation. */ + private OBase64Utils() { + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's possible, though silly, to specify + * ORDERED and URLSAFE in which case one of them will be picked, though there is no guarantee as to which one will be + * picked. + */ + private final static byte[] getAlphabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's possible, though silly, to specify + * ORDERED and URL_SAFE in which case one of them will be picked, though there is no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + /** + * Encodes up to the first three bytes of array threeBytes and returns a four-byte array in Base64 notation. The actual + * number of significant bytes in your array is given by numSigBytes. The array threeBytes needs only be as + * big as numSigBytes. Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 + * A reusable byte array to reduce array instantiation + * @param threeBytes + * the array to convert + * @param numSigBytes + * the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); + return b4; + } // end encode3to4 + + /** + *

          + * Encodes up to three bytes of the array source and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated anywhere along their length by specifying srcOffset and + * destOffset. This method does not check to make sure your arrays are large enough to accomodate srcOffset + * + 3 for the source array or destOffset + 4 for the destination array. The actual number of + * significant bytes in your array is given by numSigBytes. + *

          + *

          + * This is the lowest level of the encoding methods with all possible parameters. + *

          + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param numSigBytes + * the number of significant bytes in your array + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, int options) { + + byte[] ALPHABET = getAlphabet(options); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + /** + * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded ByteBuffer. This is an + * experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}. + * + * @param raw + * input buffer + * @param encoded + * output buffer + * @since 2.3 + */ + public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) { + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while (raw.hasRemaining()) { + int rem = Math.min(3, raw.remaining()); + raw.get(raw3, 0, rem); + OBase64Utils.encode3to4(enc4, raw3, rem, OBase64Utils.NO_OPTIONS); + encoded.put(enc4); + } // end input remaining + } + + /** + * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded CharBuffer. This is an + * experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}. + * + * @param raw + * input buffer + * @param encoded + * output buffer + * @since 2.3 + */ + public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) { + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while (raw.hasRemaining()) { + int rem = Math.min(3, raw.remaining()); + raw.get(raw3, 0, rem); + OBase64Utils.encode3to4(enc4, raw3, rem, OBase64Utils.NO_OPTIONS); + for (int i = 0; i < 4; i++) { + encoded.put((char) (enc4[i] & 0xFF)); + } + } // end input remaining + } + + /** + * Serializes an object and returns the Base64-encoded version of that serialized object. + * + *

          + * As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException. + * This is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to + * handle it. + *

          + * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject + * The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if serializedObject is null + * @since 1.4 + */ + public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException { + return encodeObject(serializableObject, NO_OPTIONS); + } // end encodeObject + + /** + * Serializes an object and returns the Base64-encoded version of that serialized object. + * + *

          + * As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException. + * This is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to + * handle it. + *

          + * + * The object is not GZip-compressed before being encoded. + *

          + * Example options: + * + *

          +   *   GZIP: gzip-compresses object before encoding it.
          +   *   DO_BREAK_LINES: break lines at 76 characters
          +   * 
          + *

          + * Example: encodeObject( myObj, Base64.GZIP ) or + *

          + * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * @param serializableObject + * The object to encode + * @param options + * Specified options + * @return The Base64-encoded object + * @see OBase64Utils#GZIP + * @see OBase64Utils#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @since 2.0 + */ + public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException { + + if (serializableObject == null) { + throw new NullPointerException("Cannot serialize a null object."); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new OBase64Utils.OutputStream(baos, ENCODE | options); + if ((options & GZIP) != 0) { + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os, 16384); // 16KB + oos = new java.io.ObjectOutputStream(gzos); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream(b64os); + } + oos.writeObject(serializableObject); + } // end try + catch (java.io.IOException e) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try { + if (oos != null) + oos.close(); + } catch (Exception e) { + } + try { + if (gzos != null) + gzos.close(); + } catch (Exception e) { + } + try { + if (b64os != null) + b64os.close(); + } catch (Exception e) { + } + try { + if (baos != null) + baos.close(); + } catch (Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + // Fall back to some Java default + return new String(baos.toByteArray()); + } // end catch + + } // end encode + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + * @param source + * The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException + * if source array is null + * @since 1.4 + */ + public static StringBuilder encodeBytes(final StringBuilder iOutput, final byte[] source) { + if (source != null) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + try { + iOutput.append(encodeBytes(source, 0, source.length, NO_OPTIONS)); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } + } + return iOutput; + } + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + * @param source + * The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException + * if source array is null + * @since 1.4 + */ + public static String encodeBytes(final byte[] source) { + if (source == null) + return null; + + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. + *

          + * Example options: + * + *

          +   *   GZIP: gzip-compresses object before encoding it.
          +   *   DO_BREAK_LINES: break lines at 76 characters
          +   *     Note: Technically, this makes your encoding non-compliant.
          +   * 
          + *

          + * Example: encodeBytes( myData, Base64.GZIP ) or + *

          + * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

          + * As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. This is new to + * v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. + *

          + * + * + * @param source + * The data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see OBase64Utils#GZIP + * @see OBase64Utils#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int options) throws java.io.IOException { + return encodeBytes(source, 0, source.length, options); + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + *

          + * As of v 2.3, if there is an error, the method will throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. + *

          + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes(byte[] source, int off, int len) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, off, len, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. + *

          + * Example options: + * + *

          +   *   GZIP: gzip-compresses object before encoding it.
          +   *   DO_BREAK_LINES: break lines at 76 characters
          +   *     Note: Technically, this makes your encoding non-compliant.
          +   * 
          + *

          + * Example: encodeBytes( myData, Base64.GZIP ) or + *

          + * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

          + * As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. This is new to + * v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. + *

          + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see OBase64Utils#GZIP + * @see OBase64Utils#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(final byte[] source, final int off, final int len, final int options) + throws java.io.IOException { + final byte[] encoded = encodeBytesToBytes(source, off, len, options); + + // Return value according to relevant encoding. + try { + return new String(encoded, PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String(encoded); + } // end catch + + } // end encodeBytes + + /* ******** D E C O D I N G M E T H O D S ******** */ + + /** + * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead of instantiating a String. This is more efficient if + * you're working with I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException + * if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes(source, 0, source.length, OBase64Utils.NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of instantiating a String. This is more + * efficient if you're working with I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see OBase64Utils#GZIP + * @see OBase64Utils#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException { + + if (source == null) { + throw new NullPointerException("Cannot serialize a null array."); + } // end if: null + + if (off < 0) { + throw new IllegalArgumentException("Cannot have negative offset: " + off); + } // end if: off < 0 + + if (len < 0) { + throw new IllegalArgumentException("Cannot have length offset: " + len); + } // end if: len < 0 + + if (off + len > source.length) { + throw new IllegalArgumentException( + String.format("Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); + } // end if: off < 0 + + // Compress? + if ((options & GZIP) != 0) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + OBase64Utils.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new OBase64Utils.OutputStream(baos, ENCODE | options); + gzos = new java.util.zip.GZIPOutputStream(b64os, 16384); // 16KB + + gzos.write(source, off, len); + gzos.close(); + } // end try + catch (java.io.IOException e) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try { + gzos.close(); + } catch (Exception e) { + } + try { + b64os.close(); + } catch (Exception e) { + } + try { + baos.close(); + } catch (Exception e) { + } + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + // int len43 = len * 4 / 3; + // byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding + if (breakLines) { + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + encode3to4(source, d + off, 3, outBuff, e, options); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, options); + e += 4; + } // end if: some padding needed + + // Only resize array if we didn't guess it right. + if (e <= outBuff.length - 1) { + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + // System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + // System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + /** + * Decodes four bytes from array source and writes the resulting bytes (up to three of them) to destination. + * The source and destination arrays can be manipulated anywhere along their length by specifying srcOffset and + * destOffset. This method does not check to make sure your arrays are large enough to accomodate srcOffset + * + 4 for the source array or destOffset + 3 for the destination array. This method returns the + * actual number of bytes that were converted from the Base64 encoding. + *

          + * This is the lowest level of the decoding methods with all possible parameters. + *

          + * + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @param options + * alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException + * if source or destination arrays are null + * @throws IllegalArgumentException + * if srcOffset or destOffset are invalid or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) { + + // Lots of error checking and exception throwing + if (source == null) { + throw new NullPointerException("Source array was null."); + } // end if + if (destination == null) { + throw new NullPointerException("Destination array was null."); + } // end if + if (srcOffset < 0 || srcOffset + 3 >= source.length) { + throw new IllegalArgumentException(String + .format("Source array with length %d cannot have offset of %d and still process four bytes", source.length, srcOffset)); + } // end if + if (destOffset < 0 || destOffset + 2 >= destination.length) { + throw new IllegalArgumentException(String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes", destination.length, destOffset)); + } // end if + + byte[] DECODABET = getDecodabet(options); + + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } + } // end decodeToBytes + + /** + * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's set. + * This is not generally a recommended method, although it is used internally as part of the decoding process. Special case: if + * len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and aren't gzipping), consider + * this method. + * + * @param source + * The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode(byte[] source) throws java.io.IOException { + byte[] decoded = null; + // try { + decoded = decode(source, 0, source.length, OBase64Utils.NO_OPTIONS); + // } catch( java.io.IOException ex ) { + // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + // } + return decoded; + } + + /** + * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's set. + * This is not generally a recommended method, although it is used internally as part of the decoding process. Special case: if + * len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and aren't gzipping), consider + * this method. + * + * @param source + * The Base64 encoded data + * @param off + * The offset of where to begin decoding + * @param len + * The length of characters to decode + * @param options + * Can specify options such as alphabet type to use + * @return decoded data + * @since 1.3 + */ + public static byte[] decode(byte[] source, int off, int len, int options) { + + // Lots of error checking and exception throwing + if (source == null) { + throw new NullPointerException("Cannot decode null source array."); + } // end if + if (off < 0 || off + len > source.length) { + throw new IllegalArgumentException( + String.format("Source array with length %d cannot have offset of %d and process %d bytes", source.length, off, len)); + } // end if + + if (len == 0) { + return OCommonConst.EMPTY_BYTE_ARRAY; + } else if (len < 4) { + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len); + } // end if + + byte[] DECODABET = getDecodabet(options); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[len34]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for (i = off; i < off + len; i++) { // Loop through source + + sbiDecode = DECODABET[source[i] & 0xFF]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if (sbiDecode >= WHITE_SPACE_ENC) { + if (sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++] = source[i]; // Save non-whitespace + if (b4Posn > 3) { // Time to decode? + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if (source[i] == EQUALS_SIGN) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new OIOException(String.format("Bad Base64 input character decimal %d in array position %d", (source[i]) & 0xFF, i)); + } // end else: + } // each input character + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } // end decode + + /** + * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it. + * + * @param s + * the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) { + return decode(s, DONT_GUNZIP); + } + + /** + * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it. + * + * @param s + * the string to decode + * @param options + * encode options such as URL_SAFE + * @return the decoded data + * @throws NullPointerException + * if s is null + * @since 1.4 + */ + public static byte[] decode(String s, int options) { + + if (s == null) { + throw new NullPointerException("Input string was null."); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes(PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uee) { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode(bytes, 0, bytes.length, options); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) { + + int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream(bytes); + gzis = new java.util.zip.GZIPInputStream(bais, 16384); // 16KB + + while ((length = gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch (java.io.IOException e) { + OLogManager.instance().error(null, "Error on decoding Base64", e); + // Just return originally-decoded bytes + } // end catch + finally { + try { + baos.close(); + } catch (Exception e) { + } + try { + gzis.close(); + } catch (Exception e) { + } + try { + bais.close(); + } catch (Exception e) { + } + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + /** + * Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error. + * + * @param encodedObject + * The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException + * if encodedObject is null + * @throws java.io.IOException + * if there is a general error + * @throws ClassNotFoundException + * if the decoded object is of a class that cannot be found by the JVM + * @since 1.5 + */ + public static Object decodeToObject(String encodedObject) throws java.io.IOException, java.lang.ClassNotFoundException { + return decodeToObject(encodedObject, NO_OPTIONS, null); + } + + /** + * Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error. If + * loader is not null, it will be the class loader used when deserializing. + * + * @param encodedObject + * The Base64 data to decode + * @param options + * Various parameters related to decoding + * @param loader + * Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException + * if encodedObject is null + * @throws java.io.IOException + * if there is a general error + * @throws ClassNotFoundException + * if the decoded object is of a class that cannot be found by the JVM + * @since 2.3.4 + */ + public static Object decodeToObject(String encodedObject, int options, final ClassLoader loader) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode(encodedObject, options); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream(objBytes); + + // If no custom class loader is provided, use Java's builtin OIS. + if (loader == null) { + ois = new java.io.ObjectInputStream(bais); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais) { + @Override + public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, ClassNotFoundException { + Class c = Class.forName(streamClass.getName(), false, loader); + if (c == null) { + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch (java.lang.ClassNotFoundException e) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try { + bais.close(); + } catch (Exception e) { + } + try { + ois.close(); + } catch (Exception e) { + } + } // end finally + + return obj; + } // end decodeObject + + /** + * Convenience method for encoding data to a file. + * + *

          + * As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. + *

          + * + * @param dataToEncode + * byte array of data to encode in base64 form + * @param filename + * Filename for saving encoded data + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException { + + if (dataToEncode == null) { + throw new NullPointerException("Data to encode was null."); + } // end iff + + OBase64Utils.OutputStream bos = null; + try { + bos = new OBase64Utils.OutputStream(new java.io.FileOutputStream(filename), OBase64Utils.ENCODE); + bos.write(dataToEncode); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try { + bos.close(); + } catch (Exception e) { + } + } // end finally + + } // end encodeToFile + + /** + * Convenience method for decoding data to a file. + * + *

          + * As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. + *

          + * + * @param dataToDecode + * Base64-encoded data as a string + * @param filename + * Filename for saving decoded data + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException { + + OBase64Utils.OutputStream bos = null; + try { + bos = new OBase64Utils.OutputStream(new java.io.FileOutputStream(filename), OBase64Utils.DECODE); + bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try { + bos.close(); + } catch (Exception e) { + } + } // end finally + + } // end decodeToFile + + /** + * Convenience method for reading a base64-encoded file and decoding it. + * + *

          + * As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. + *

          + * + * @param filename + * Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile(String filename) throws java.io.IOException { + + byte[] decodedData = null; + OBase64Utils.InputStream bis = null; + try { + // Set up some useful variables + java.io.File file = new java.io.File(filename); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if (file.length() > Integer.MAX_VALUE) { + throw new java.io.IOException("File is too big for this convenience method (" + file.length() + " bytes)."); + } // end if: file too big for int index + buffer = new byte[(int) file.length()]; + + // Open a stream + bis = new OBase64Utils.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), OBase64Utils.DECODE); + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[length]; + System.arraycopy(buffer, 0, decodedData, 0, length); + + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try { + bis.close(); + } catch (Exception e) { + } + } // end finally + + return decodedData; + } // end decodeFromFile + + /** + * Convenience method for reading a binary file and base64-encoding it. + * + *

          + * As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. + *

          + * + * @param filename + * Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static String encodeFromFile(String filename) throws java.io.IOException { + + String encodedData = null; + OBase64Utils.InputStream bis = null; + try { + // Set up some useful variables + java.io.File file = new java.io.File(filename); + byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need max() for math on small files (v2.2.1); Need + // +1 for a few corner cases (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new OBase64Utils.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), OBase64Utils.ENCODE); + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String(buffer, 0, length, OBase64Utils.PREFERRED_ENCODING); + + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try { + bis.close(); + } catch (Exception e) { + } + } // end finally + + return encodedData; + } // end encodeFromFile + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + /** + * Reads infile and encodes it to outfile. + * + * @param infile + * Input file + * @param outfile + * Output file + * @throws java.io.IOException + * if there is an error + * @since 2.2 + */ + public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException { + + String encoded = OBase64Utils.encodeFromFile(infile); + java.io.OutputStream out = null; + try { + out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); + out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output. + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { + out.close(); + } catch (Exception ex) { + } + } // end finally + } // end encodeFileToFile + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + /** + * Reads infile and decodes it to outfile. + * + * @param infile + * Input file + * @param outfile + * Output file + * @throws java.io.IOException + * if there is an error + * @since 2.2 + */ + public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException { + + byte[] decoded = OBase64Utils.decodeFromFile(infile); + java.io.OutputStream out = null; + try { + out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); + out.write(decoded); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { + out.close(); + } catch (Exception ex) { + } + } // end finally + } // end decodeFileToFile + +} // end class Base64 diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OBinaryProtocol.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OBinaryProtocol.java new file mode 100755 index 00000000000..2a78da4bf5f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OBinaryProtocol.java @@ -0,0 +1,262 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Static helper class to transform any kind of basic data in bytes and vice versa. + * + * @author Luca Garulli + */ +public class OBinaryProtocol { + + public static final int SIZE_BYTE = 1; + public static final int SIZE_CHAR = 2; + public static final int SIZE_SHORT = 2; + public static final int SIZE_INT = 4; + public static final int SIZE_LONG = 8; + + public static byte[] char2bytes(final char value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 0) & 0xFF); + return b; + } + + public static int long2bytes(final long value, final OutputStream iStream) throws IOException { + final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1; + + iStream.write((int) (value >>> 56) & 0xFF); + iStream.write((int) (value >>> 48) & 0xFF); + iStream.write((int) (value >>> 40) & 0xFF); + iStream.write((int) (value >>> 32) & 0xFF); + iStream.write((int) (value >>> 24) & 0xFF); + iStream.write((int) (value >>> 16) & 0xFF); + iStream.write((int) (value >>> 8) & 0xFF); + iStream.write((int) (value >>> 0) & 0xFF); + + return beginOffset; + } + + public static byte[] long2bytes(final long value) { + return OBinaryProtocol.long2bytes(value, new byte[8], 0); + } + + public static byte[] long2bytes(final long value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 56) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 48) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 40) & 0xFF); + b[iBeginOffset + 3] = (byte) ((value >>> 32) & 0xFF); + b[iBeginOffset + 4] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 5] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 6] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 7] = (byte) ((value >>> 0) & 0xFF); + return b; + } + + public static int int2bytes(final int value, final OutputStream iStream) throws IOException { + final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1; + + iStream.write((value >>> 24) & 0xFF); + iStream.write((value >>> 16) & 0xFF); + iStream.write((value >>> 8) & 0xFF); + iStream.write((value >>> 0) & 0xFF); + + return beginOffset; + } + + public static byte[] int2bytes(final int value) { + return OBinaryProtocol.int2bytes(value, new byte[4], 0); + } + + public static byte[] int2bytes(final int value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 24) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 16) & 0xFF); + b[iBeginOffset + 2] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 3] = (byte) ((value >>> 0) & 0xFF); + return b; + } + + public static int short2bytes(final short value, final OutputStream iStream) throws IOException { + final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1; + iStream.write((value >>> 8) & 0xFF); + iStream.write((value >>> 0) & 0xFF); + return beginOffset; + } + + public static byte[] short2bytes(final short value) { + return OBinaryProtocol.short2bytes(value, new byte[2], 0); + } + + public static byte[] short2bytes(final short value, final byte[] b, final int iBeginOffset) { + b[iBeginOffset] = (byte) ((value >>> 8) & 0xFF); + b[iBeginOffset + 1] = (byte) ((value >>> 0) & 0xFF); + return b; + } + + public static long bytes2long(final byte[] b) { + return OBinaryProtocol.bytes2long(b, 0); + } + + public static long bytes2long(final InputStream iStream) throws IOException { + return ((long) (0xff & iStream.read()) << 56 | (long) (0xff & iStream.read()) << 48 | (long) (0xff & iStream.read()) << 40 + | (long) (0xff & iStream.read()) << 32 | (long) (0xff & iStream.read()) << 24 | (0xff & iStream.read()) << 16 + | (0xff & iStream.read()) << 8 | (0xff & iStream.read())); + } + + public static long bytes2long(final byte[] b, final int offset) { + return ((0xff & b[offset + 7]) | (0xff & b[offset + 6]) << 8 | (0xff & b[offset + 5]) << 16 + | (long) (0xff & b[offset + 4]) << 24 | (long) (0xff & b[offset + 3]) << 32 | (long) (0xff & b[offset + 2]) << 40 + | (long) (0xff & b[offset + 1]) << 48 | (long) (0xff & b[offset]) << 56); + } + + /** + * Convert the byte array to an int. + * + * @param b The byte array + * @return The integer + */ + public static int bytes2int(final byte[] b) { + return bytes2int(b, 0); + } + + public static int bytes2int(final InputStream iStream) throws IOException { + return ((0xff & iStream.read()) << 24 | (0xff & iStream.read()) << 16 | (0xff & iStream.read()) << 8 | (0xff & iStream.read())); + } + + /** + * Convert the byte array to an int starting from the given offset. + * + * @param b The byte array + * @param offset The array offset + * @return The integer + */ + public static int bytes2int(final byte[] b, final int offset) { + return (b[offset]) << 24 | (0xff & b[offset + 1]) << 16 | (0xff & b[offset + 2]) << 8 | ((0xff & b[offset + 3])); + } + + public static int bytes2short(final InputStream iStream) throws IOException { + return (short) ((iStream.read() << 8) | (iStream.read() & 0xff)); + } + + public static short bytes2short(final byte[] b, final int offset) { + return (short) ((b[offset] << 8) | (b[offset + 1] & 0xff)); + } + + public static char bytes2char(final byte[] b, final int offset) { + return (char) ((b[offset] << 8) + (b[offset + 1] & 0xff)); + } + + public static String bytes2string(final byte[] iInput) { + if (iInput == null) + return null; + + return OBinaryProtocol.bytes2string(iInput, 0, iInput.length); + } + + public static String bytes2string(final byte[] input, final int iBeginOffset, final int iLenght) { + final char[] output = new char[iLenght]; + // index input[] + int i = iBeginOffset; + // index output[] + int j = 0; + while (i < iLenght + iBeginOffset) { + // get next byte unsigned + int b = input[i++] & 0xff; + // classify based on the high order 3 bits + switch (b >>> 5) { + default: + // one byte encoding + // 0xxxxxxx + // use just low order 7 bits + // 00000000 0xxxxxxx + output[j++] = (char) (b & 0x7f); + break; + case 6: + // two byte encoding + // 110yyyyy 10xxxxxx + // use low order 6 bits + int y = b & 0x1f; + // use low order 6 bits of the next byte + // It should have high order bits 10, which we don't check. + int x = input[i++] & 0x3f; + // 00000yyy yyxxxxxx + output[j++] = (char) (y << 6 | x); + break; + case 7: + // three byte encoding + // 1110zzzz 10yyyyyy 10xxxxxx + assert (b & 0x10) == 0 : "UTF8Decoder does not handle 32-bit characters"; + // use low order 4 bits + final int z = b & 0x0f; + // use low order 6 bits of the next byte + // It should have high order bits 10, which we don't check. + y = input[i++] & 0x3f; + // use low order 6 bits of the next byte + // It should have high order bits 10, which we don't check. + x = input[i++] & 0x3f; + // zzzzyyyy yyxxxxxx + final int asint = (z << 12 | y << 6 | x); + output[j++] = (char) asint; + break; + }// end switch + }// end while + return new String(output, 0/* offset */, j/* count */); + } + + public static final byte[] string2bytes(final String iInputText) { + if (iInputText == null) + return null; + + final int len = iInputText.length(); + + // worst case, all chars could require 3-byte encodings. + final byte[] output = new byte[len * 3]; + + // index output[] + int j = 0; + + for (int i = 0; i < len; i++) { + int c = iInputText.charAt(i); + + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = (byte) c; + } else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (byte) (0xC0 | c >> 6); + output[j++] = (byte) (0x80 | c & 0x3F); + } else { + // 12-16 bits done in 3 bytes + output[j++] = (byte) (0xE0 | c >> 12); + output[j++] = (byte) (0x80 | c >> 6 & 0x3F); + output[j++] = (byte) (0x80 | c & 0x3F); + } + }// end for + // Prune back our byte array. For efficiency we could hand item back + // partly filled, which is only a minor inconvenience to the caller + // most of the time to save copying the array. + final byte[] chopped = new byte[j]; + System.arraycopy(output, 0, chopped, 0, j/* length */); + return chopped; + }// end encode +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/ODocumentSerializable.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/ODocumentSerializable.java new file mode 100644 index 00000000000..f7624769dbc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/ODocumentSerializable.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Interface for objects which are hold inside of document as field values and can serialize yourself into document. In such way it + * is possible to serialize complex types and do not break compatibility with non-Java binary drivers. + * + * After serialization into document additional field {@link #CLASS_NAME} will be added. This field contains value of class of + * original object. + * + * During deserialization of embedded object if embedded document contains {@link #CLASS_NAME} field we try to find class with given + * name and only if this class implements {@link ODocumentSerializable} interface it will be converted to the object. So it is + * pretty safe to use field with {@link #CLASS_NAME} in ordinary documents if it is needed. + * + * Class which implements this interface should have public no-arguments constructor. + * + * + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 3/27/14 + */ +public interface ODocumentSerializable { + String CLASS_NAME = "__orientdb_serilized_class__ "; + + ODocument toDocument(); + + void fromDocument(ODocument document); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryInputStream.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryInputStream.java new file mode 100755 index 00000000000..f9816e9d4be --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryInputStream.java @@ -0,0 +1,236 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import com.orientechnologies.common.util.OArrays; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Class to parse and write buffers in very fast way. + * + * @author Luca Garulli + * + */ +public class OMemoryInputStream extends InputStream { + private byte[] buffer; + private int offset = 0; + private int length; + private int position; + + public OMemoryInputStream() { + } + + public OMemoryInputStream(final byte[] iBuffer) { + setSource(iBuffer); + } + + public OMemoryInputStream(final byte[] iBuffer, final int iOffset, final int iLength) { + setSource(iBuffer, iOffset, iLength); + } + + public byte[] getAsByteArrayFixed(final int iSize) throws IOException { + if (position >= length) + return null; + + final byte[] portion = OArrays.copyOfRange(buffer, position, position + iSize); + position += iSize; + + return portion; + } + + @Override + public int available() throws IOException { + return length - position; + } + + /** + * Browse the stream but just return the begin of the byte array. This is used to lazy load the information only when needed. + * + */ + public int getAsByteArrayOffset() { + if (position >= length) + return -1; + + final int begin = position; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT + size; + + return begin; + } + + @Override + public int read() throws IOException { + if (position >= length) + return -1; + + return buffer[position++] & 0xFF; + } + + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + if (position >= length) + return -1; + + int newLen; + if (position + len >= length) + newLen = length - position; + else + newLen = len; + + if (off + newLen >= b.length) + newLen = b.length - off; + + if (newLen <= 0) + return 0; + + System.arraycopy(buffer, position, b, off, newLen); + position += newLen; + + return newLen; + } + + public byte[] getAsByteArray(int iOffset) throws IOException { + if (buffer == null || iOffset >= length) + return null; + + final int size = OBinaryProtocol.bytes2int(buffer, iOffset); + + if (size == 0) + return null; + + iOffset += OBinaryProtocol.SIZE_INT; + + return OArrays.copyOfRange(buffer, iOffset, iOffset + size); + } + + public byte[] getAsByteArray() throws IOException { + if (position >= length) + return null; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + + final byte[] portion = OArrays.copyOfRange(buffer, position, position + size); + position += size; + + return portion; + } + + public boolean getAsBoolean() throws IOException { + return buffer[position++] == 1; + } + + public char getAsChar() throws IOException { + final char value = OBinaryProtocol.bytes2char(buffer, position); + position += OBinaryProtocol.SIZE_CHAR; + return value; + } + + public byte getAsByte() throws IOException { + return buffer[position++]; + } + + public long getAsLong() throws IOException { + final long value = OBinaryProtocol.bytes2long(buffer, position); + position += OBinaryProtocol.SIZE_LONG; + return value; + } + + public int getAsInteger() throws IOException { + final int value = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + return value; + } + + public short getAsShort() throws IOException { + final short value = OBinaryProtocol.bytes2short(buffer, position); + position += OBinaryProtocol.SIZE_SHORT; + return value; + } + + public void close() { + buffer = null; + } + + public byte peek() { + return buffer[position]; + } + + public void setSource(final byte[] iBuffer) { + buffer = iBuffer; + position = 0; + offset = 0; + length = iBuffer.length; + } + + public void setSource(final byte[] iBuffer, final int iOffset, final int iLength) { + buffer = iBuffer; + position = iOffset; + offset = iOffset; + length = iLength + iOffset; + } + + public OMemoryInputStream jump(final int iOffset) { + position += iOffset; + return this; + } + + public byte[] copy() { + if (buffer == null) + return null; + + final int size = position > 0 ? position : buffer.length; + + final byte[] copy = new byte[size]; + System.arraycopy(buffer, 0, copy, 0, size); + return copy; + } + + public int getVariableSize() throws IOException { + if (position >= length) + return -1; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + + return size; + } + + public int getSize() { + return buffer.length; + } + + public int getPosition() { + return position; + } + + @Override + public synchronized void reset() throws IOException { + position = offset; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryStream.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryStream.java new file mode 100755 index 00000000000..89559064c44 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OMemoryStream.java @@ -0,0 +1,483 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue; +import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE; +import com.orientechnologies.common.util.OArrays; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.exception.OSerializationException; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * Class to parse and write buffers in very fast way. + * + * @author Luca Garulli + * @deprecated use {@link OByteArrayOutputStream} instead. + */ + +@Deprecated +public class OMemoryStream extends OutputStream { + public static final int DEF_SIZE = 1024; + + private byte[] buffer; + private int position; + private Charset charset = Charset.forName("utf8"); + + private static final int NATIVE_COPY_THRESHOLD = 9; + private static long metricResize = 0; + + static { + Orient.instance().getProfiler() + .registerHookValue("system.memory.stream.resize", "Number of resizes of memory stream buffer", METRIC_TYPE.COUNTER, + new OProfilerHookValue() { + public Object getValue() { + return metricResize; + } + }); + } + + public OMemoryStream() { + this(DEF_SIZE); + } + + /** + * Callee takes ownership of 'buf'. + */ + public OMemoryStream(final int initialCapacity) { + buffer = new byte[initialCapacity]; + } + + public OMemoryStream(byte[] stream) { + buffer = stream; + } + + /** + * Move bytes left or right of an offset. + * + * @param iFrom Starting position + * @param iPosition Offset to the iFrom value: positive values mean move right, otherwise move left + */ + public void move(final int iFrom, final int iPosition) { + if (iPosition == 0) + return; + + final int to = iFrom + iPosition; + final int size = iPosition > 0 ? buffer.length - to : buffer.length - iFrom; + + System.arraycopy(buffer, iFrom, buffer, to, size); + } + + public void copyFrom(final OMemoryStream iSource, final int iSize) { + if (iSize < 0) + return; + + assureSpaceFor(position + iSize); + System.arraycopy(iSource.buffer, iSource.position, buffer, position, iSize); + } + + public final void writeTo(final OutputStream out) throws IOException { + out.write(buffer, 0, position); + } + + public final byte[] getInternalBuffer() { + return buffer; + } + + /** + * Returns the used buffer as byte[]. + * + * @return [result.length = size()] + */ + public final byte[] toByteArray() { + if (position == buffer.length - 1) + // 100% USED, RETURN THE FULL BUFFER + return buffer; + + final int pos = position; + + final byte[] destinBuffer = new byte[pos]; + final byte[] sourceBuffer = buffer; + + if (pos < NATIVE_COPY_THRESHOLD) + for (int i = 0; i < pos; ++i) + destinBuffer[i] = sourceBuffer[i]; + else + System.arraycopy(sourceBuffer, 0, destinBuffer, 0, pos); + + return destinBuffer; + } + + /** + * Does not reduce the current capacity. + */ + public final void reset() { + position = 0; + } + + // OutputStream: + + @Override + public final void write(final int b) { + assureSpaceFor(OBinaryProtocol.SIZE_BYTE); + buffer[position++] = (byte) b; + } + + @Override + public final void write(final byte[] iBuffer, final int iOffset, final int iLength) { + final int pos = position; + final int tot = pos + iLength; + + assureSpaceFor(iLength); + + final byte[] localBuffer = buffer; + + if (iLength < NATIVE_COPY_THRESHOLD) + for (int i = 0; i < iLength; ++i) + localBuffer[pos + i] = iBuffer[iOffset + i]; + else + System.arraycopy(iBuffer, iOffset, localBuffer, pos, iLength); + + position = tot; + } + + /** + * Equivalent to {@link #reset()}. + */ + @Override + public final void close() { + reset(); + } + + public final void setAsFixed(final byte[] iContent) { + if (iContent == null) + return; + write(iContent, 0, iContent.length); + } + + /** + * Append byte[] to the stream. + * + * @param iContent + * @return The begin offset of the appended content + * @throws IOException + */ + public int set(final byte[] iContent) { + if (iContent == null) + return -1; + + final int begin = position; + + assureSpaceFor(OBinaryProtocol.SIZE_INT + iContent.length); + + OBinaryProtocol.int2bytes(iContent.length, buffer, position); + position += OBinaryProtocol.SIZE_INT; + write(iContent, 0, iContent.length); + + return begin; + } + + public void remove(final int iBegin, final int iEnd) { + if (iBegin > iEnd) + throw new IllegalArgumentException("Begin is bigger than end"); + + if (iEnd > buffer.length) + throw new IndexOutOfBoundsException("Position " + iEnd + " is greater than the buffer length (" + buffer.length + ")"); + + System.arraycopy(buffer, iEnd, buffer, iBegin, buffer.length - iEnd); + } + + public void set(final byte iContent) { + write(iContent); + } + + public final int setCustom(final String iContent) { + try { + return set(iContent.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("error encoding string"),e); + } + } + + public final int setUtf8(final String iContent) { + return set(iContent.getBytes(charset)); + } + + public int set(final boolean iContent) { + final int begin = position; + write((byte) (iContent ? 1 : 0)); + return begin; + } + + public int set(final char iContent) { + assureSpaceFor(OBinaryProtocol.SIZE_CHAR); + final int begin = position; + OBinaryProtocol.char2bytes(iContent, buffer, position); + position += OBinaryProtocol.SIZE_CHAR; + return begin; + } + + public int set(final int iContent) { + assureSpaceFor(OBinaryProtocol.SIZE_INT); + final int begin = position; + OBinaryProtocol.int2bytes(iContent, buffer, position); + position += OBinaryProtocol.SIZE_INT; + return begin; + } + + public int set(final long iContent) { + assureSpaceFor(OBinaryProtocol.SIZE_LONG); + final int begin = position; + OBinaryProtocol.long2bytes(iContent, buffer, position); + position += OBinaryProtocol.SIZE_LONG; + return begin; + } + + public int set(final short iContent) { + assureSpaceFor(OBinaryProtocol.SIZE_SHORT); + final int begin = position; + OBinaryProtocol.short2bytes(iContent, buffer, position); + position += OBinaryProtocol.SIZE_SHORT; + return begin; + } + + public int getPosition() { + return position; + } + + public OMemoryStream setPosition(final int iPosition) { + position = iPosition; + return this; + } + + private void assureSpaceFor(final int iLength) { + final byte[] localBuffer = buffer; + final int pos = position; + final int capacity = position + iLength; + + final int bufferLength = localBuffer.length; + + if (bufferLength < capacity) { + metricResize++; + + final byte[] newbuf = new byte[Math.max(bufferLength << 1, capacity)]; + + if (pos < NATIVE_COPY_THRESHOLD) + for (int i = 0; i < pos; ++i) + newbuf[i] = localBuffer[i]; + else + System.arraycopy(localBuffer, 0, newbuf, 0, pos); + + buffer = newbuf; + } + } + + /** + * Jumps bytes positioning forward of passed bytes. + * + * @param iLength Bytes to jump + */ + public void fill(final int iLength) { + assureSpaceFor(iLength); + position += iLength; + } + + /** + * Fills the stream from current position writing iLength times the iFiller byte + * + * @param iLength Bytes to jump + * @param iFiller Byte to use to fill the space + */ + public void fill(final int iLength, final byte iFiller) { + assureSpaceFor(iLength); + Arrays.fill(buffer, position, position + iLength, iFiller); + position += iLength; + } + + public OMemoryStream jump(final int iOffset) { + if (iOffset > buffer.length) + throw new IndexOutOfBoundsException("Offset " + iOffset + " is greater than the buffer size " + buffer.length); + position = iOffset; + return this; + } + + public byte[] getAsByteArrayFixed(final int iSize) { + if (position >= buffer.length) + return null; + + final byte[] portion = OArrays.copyOfRange(buffer, position, position + iSize); + position += iSize; + + return portion; + } + + /** + * Browse the stream but just return the begin of the byte array. This is used to lazy load the information only when needed. + */ + public int getAsByteArrayOffset() { + if (position >= buffer.length) + return -1; + + final int begin = position; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT + size; + + return begin; + } + + public int read() { + return buffer[position++]; + } + + public int read(final byte[] b) { + return read(b, 0, b.length); + } + + public int read(final byte[] b, final int off, final int len) { + if (position >= buffer.length) + return 0; + + System.arraycopy(buffer, position, b, off, len); + position += len; + + return len; + } + + public byte[] getAsByteArray(int iOffset) { + if (buffer == null || iOffset >= buffer.length) + return null; + + final int size = OBinaryProtocol.bytes2int(buffer, iOffset); + + if (size == 0) + return null; + + iOffset += OBinaryProtocol.SIZE_INT; + + return OArrays.copyOfRange(buffer, iOffset, iOffset + size); + } + + public byte[] getAsByteArray() { + if (position >= buffer.length) + return null; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + + final byte[] portion = OArrays.copyOfRange(buffer, position, position + size); + position += size; + + return portion; + } + + /** + * Returns the available bytes to read. + */ + public int available() { + return buffer.length - position; + } + + public String getAsString() { + if (position >= buffer.length) + return null; + + final int size = getVariableSize(); + String str = new String(buffer, position, size, charset); + position += size; + return str; + } + + public boolean getAsBoolean() { + return buffer[position++] == 1; + } + + public char getAsChar() { + final char value = OBinaryProtocol.bytes2char(buffer, position); + position += OBinaryProtocol.SIZE_CHAR; + return value; + } + + public byte getAsByte() { + return buffer[position++]; + } + + public long getAsLong() { + final long value = OBinaryProtocol.bytes2long(buffer, position); + position += OBinaryProtocol.SIZE_LONG; + return value; + } + + public int getAsInteger() { + final int value = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + return value; + } + + public short getAsShort() { + final short value = OBinaryProtocol.bytes2short(buffer, position); + position += OBinaryProtocol.SIZE_SHORT; + return value; + } + + public byte peek() { + return buffer[position]; + } + + public void setSource(final byte[] iBuffer) { + buffer = iBuffer; + position = 0; + } + + public byte[] copy() { + if (buffer == null) + return null; + + final int size = position > 0 ? position : buffer.length; + + final byte[] copy = new byte[size]; + System.arraycopy(buffer, 0, copy, 0, size); + return copy; + } + + public int getVariableSize() { + if (position >= buffer.length) + return -1; + + final int size = OBinaryProtocol.bytes2int(buffer, position); + position += OBinaryProtocol.SIZE_INT; + + return size; + } + + public int getSize() { + return buffer.length; + } + + public final int size() { + return position; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OSerializableStream.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OSerializableStream.java new file mode 100644 index 00000000000..19a77f0368e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OSerializableStream.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import java.io.Serializable; + +import com.orientechnologies.orient.core.exception.OSerializationException; + +/** + * Base interface of serialization. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OSerializableStream extends Serializable { + /** + * Marshalls the object. Transforms the current object in byte[] form to being stored or transferred over the network. + * + * @return The byte array representation of the object + * @see #fromStream(byte[]) + * @throws OSerializationException + * if the marshalling does not succeed + */ + byte[] toStream() throws OSerializationException; + + /** + * Unmarshalls the object. Fills the current object with the values contained in the byte array representation restoring a + * previous state. Usually byte[] comes from the storage or network. + * + * @param iStream + * byte array representation of the object + * @return The Object instance itself giving a "fluent interface". Useful to call multiple methods in chain. + * @throws OSerializationException + * if the unmarshalling does not succeed + */ + OSerializableStream fromStream(byte[] iStream) throws OSerializationException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamable.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamable.java new file mode 100644 index 00000000000..0ba01ec75f2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamable.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * Base interface of serialization that uses DataOutput and DataInput Java interfaces. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + * + */ +public interface OStreamable { + void toStream(DataOutput out) throws IOException; + + void fromStream(DataInput in) throws IOException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamableHelper.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamableHelper.java new file mode 100644 index 00000000000..b0f1823401c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/OStreamableHelper.java @@ -0,0 +1,156 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OSerializationException; + +import java.io.*; + +/** + * Helper class to serialize OStreamable objects. + * + * @author Luca Garulli (l.garulli--at--orientdb.com) + * + */ +public class OStreamableHelper { + final static byte NULL = 0; + final static byte STREAMABLE = 1; + final static byte SERIALIZABLE = 2; + + final static byte STRING = 10; + final static byte INTEGER = 11; + final static byte SHORT = 12; + final static byte LONG = 13; + final static byte BOOLEAN = 14; + + private static ClassLoader streamableClassLoader; + + /** + * Set the preferred {@link ClassLoader} used to load streamable types. + */ + public static void setStreamableClassLoader(/* @Nullable */final ClassLoader streamableClassLoader) { + OStreamableHelper.streamableClassLoader = streamableClassLoader; + } + + public static void toStream(final DataOutput out, final Object object) throws IOException { + if (object == null) + out.writeByte(NULL); + else if (object instanceof OStreamable) { + out.writeByte(STREAMABLE); + out.writeUTF(object.getClass().getName()); + ((OStreamable) object).toStream(out); + } else if (object instanceof String) { + out.writeByte(STRING); + out.writeUTF((String) object); + } else if (object instanceof Integer) { + out.writeByte(INTEGER); + out.writeInt((Integer) object); + } else if (object instanceof Short) { + out.writeByte(SHORT); + out.writeShort((Short) object); + } else if (object instanceof Long) { + out.writeByte(LONG); + out.writeLong((Long) object); + } else if (object instanceof Boolean) { + out.writeByte(BOOLEAN); + out.writeBoolean((Boolean) object); + } else if (object instanceof Serializable) { + out.writeByte(SERIALIZABLE); + final ByteArrayOutputStream mem = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(mem); + try { + oos.writeObject(object); + oos.flush(); + final byte[] buffer = mem.toByteArray(); + out.writeInt(buffer.length); + out.write(buffer); + } finally { + oos.close(); + mem.close(); + } + } else + throw new OSerializationException("Object not supported: " + object); + + } + + public static Object fromStream(final DataInput in) throws IOException { + Object object = null; + + final byte objectType = in.readByte(); + switch (objectType) { + case NULL: + return null; + case STREAMABLE: + final String payloadClassName = in.readUTF(); + try { + if (streamableClassLoader != null) { + object = streamableClassLoader.loadClass(payloadClassName).newInstance(); + } else { + object = Class.forName(payloadClassName).newInstance(); + } + ((OStreamable) object).fromStream(in); + } catch (Exception e) { + throw OException.wrapException(new OSerializationException("Cannot unmarshall object from distributed request"), e); + } + break; + case SERIALIZABLE: + final byte[] buffer = new byte[in.readInt()]; + in.readFully(buffer); + final ByteArrayInputStream mem = new ByteArrayInputStream(buffer); + final ObjectInputStream ois; + if (streamableClassLoader != null) { + ois = new ObjectInputStream(mem) { + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + return streamableClassLoader.loadClass(desc.getName()); + } + }; + } else { + ois = new ObjectInputStream(mem); + } + try { + try { + object = ois.readObject(); + } catch (ClassNotFoundException e) { + throw OException.wrapException(new OSerializationException("Cannot unmarshall object from distributed request"), e); + } + } finally { + ois.close(); + mem.close(); + } + break; + case STRING: + return in.readUTF(); + case INTEGER: + return in.readInt(); + case SHORT: + return in.readShort(); + case LONG: + return in.readLong(); + case BOOLEAN: + return in.readBoolean(); + default: + throw new OSerializationException("Object type not supported: " + objectType); + } + return object; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONReader.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONReader.java new file mode 100644 index 00000000000..cf341939994 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONReader.java @@ -0,0 +1,329 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.text.ParseException; +import java.util.Arrays; + +public class OJSONReader { + public static final char NEW_LINE = '\n'; + public static final char[] DEFAULT_JUMP = new char[] { ' ', '\r', '\n', '\t' }; + public static final char[] DEFAULT_SKIP = new char[] { '\r', '\n', '\t' }; + public static final char[] BEGIN_OBJECT = new char[] { '{' }; + public static final char[] END_OBJECT = new char[] { '}' }; + public static final char[] FIELD_ASSIGNMENT = new char[] { ':' }; + public static final char[] BEGIN_STRING = new char[] { '"' }; + public static final char[] COMMA_SEPARATOR = new char[] { ',' }; + public static final char[] NEXT_IN_OBJECT = new char[] { ',', '}' }; + public static final char[] NEXT_IN_ARRAY = new char[] { ',', ']' }; + public static final char[] NEXT_OBJ_IN_ARRAY = new char[] { '{', ']' }; + public static final char[] ANY_NUMBER = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + public static final char[] BEGIN_COLLECTION = new char[] { '[' }; + public static final char[] END_COLLECTION = new char[] { ']' }; + private BufferedReader in; + private int cursor = 0; + private int lineNumber = 0; + private int columnNumber = 0; + private StringBuilder buffer = new StringBuilder(16384); // 16KB + private String value; + private char c; + private char lastCharacter; + private Character missedChar; + + public OJSONReader(Reader iIn) { + this.in = new BufferedReader(iIn); + } + + public int getCursor() { + return cursor; + } + + public OJSONReader checkContent(final String iExpected) throws ParseException { + if (!value.equals(iExpected)) + throw new ParseException("Expected content is " + iExpected + " but found " + value, cursor); + return this; + } + + public boolean isContent(final String iExpected) { + return value.equals(iExpected); + } + + public int readInteger(final char[] iUntil) throws IOException, ParseException { + return readNumber(iUntil, false); + } + + public int readNumber(final char[] iUntil, final boolean iInclude) throws IOException, ParseException { + if (readNext(iUntil, iInclude) == null) + throw new ParseException("Expected integer", cursor); + + return Integer.parseInt(value.trim()); + } + + public String readString(final char[] iUntil) throws IOException, ParseException { + return readString(iUntil, false); + } + + public String readString(final char[] iUntil, final boolean iInclude) throws IOException, ParseException { + return readString(iUntil, iInclude, DEFAULT_JUMP, null); + } + + public String readString(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars) + throws IOException, ParseException { + if (readNext(iUntil, iInclude, iJumpChars, iSkipChars) == null) + return null; + + if (!iInclude && value.startsWith("\"")) { + return value.substring(1, value.lastIndexOf("\"")); + } + + return value; + } + + public String readString(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars, boolean preserveQuotes) + throws IOException, ParseException { + if (readNext(iUntil, iInclude, iJumpChars, iSkipChars, preserveQuotes) == null) + return null; + + if (!iInclude && value.startsWith("\"")) { + return value.substring(1, value.lastIndexOf("\"")); + } + + return value; + } + + + public boolean readBoolean(final char[] nextInObject) throws IOException, ParseException { + return Boolean.parseBoolean(readString(nextInObject, false, DEFAULT_JUMP, DEFAULT_JUMP)); + } + + public OJSONReader readNext(final char[] iUntil) throws IOException, ParseException { + readNext(iUntil, false); + return this; + } + + public OJSONReader readNext(final char[] iUntil, final boolean iInclude) throws IOException, ParseException { + readNext(iUntil, iInclude, DEFAULT_JUMP, null); + return this; + } + public OJSONReader readNext(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars) + throws IOException, ParseException { + readNext(iUntil, iInclude, iJumpChars, iSkipChars, true); + return this; + } + + public OJSONReader readNext(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars, boolean preserveQuotes) + throws IOException, ParseException { + if (!in.ready()) + return this; + + jump(iJumpChars); + + if (!in.ready()) + return this; + + // READ WHILE THERE IS SOMETHING OF AVAILABLE + int openBrackets = 0; + char beginStringChar = ' '; + boolean encodeMode = false; + boolean found; + do { + found = false; + if (beginStringChar == ' ') { + // NO INSIDE A STRING + if (openBrackets == 0) { + // FIND FOR SEPARATOR + for (char u : iUntil) { + if (u == c) { + found = true; + break; + } + } + } + + if (c == '\'' || c == '"' && !encodeMode) + // BEGIN OF STRING + beginStringChar = c; + else if (c == '{') + // OPEN EMBEDDED + openBrackets++; + else if (c == '}' && openBrackets > 0) + // CLOSE EMBEDDED + openBrackets--; + + if (!found && openBrackets == 0) { + // FIND FOR SEPARATOR + for (char u : iUntil) { + if (u == c) { + found = true; + break; + } + } + } + } else if (beginStringChar == c && !encodeMode) + // END OF STRING + beginStringChar = ' '; + + if (c == '\\' && !encodeMode) + encodeMode = true; + else + encodeMode = false; + + if (!found) { + final int read = nextChar(); + if (read == -1) + break; + + // APPEND IT + c = (char) read; + + boolean skip = false; + if (iSkipChars != null) + for (char j : iSkipChars) { + if (j == c) { + skip = true; + break; + } + } + + if (!skip && (preserveQuotes || !encodeMode)) { + lastCharacter = c; + buffer.append(c); + } + } + + } while (!found && in.ready()); + + if (buffer.length() == 0) + throw new ParseException("Expected characters '" + Arrays.toString(iUntil) + "' not found", cursor); + + if (!iInclude) + buffer.setLength(buffer.length() - 1); + + value = buffer.toString(); + return this; + } + + public int jump(final char[] iJumpChars) throws IOException, ParseException { + buffer.setLength(0); + + if (!in.ready()) + return 0; + + // READ WHILE THERE IS SOMETHING OF AVAILABLE + boolean go = true; + while (go && in.ready()) { + int read = nextChar(); + if (read == -1) + return -1; + + go = false; + for (char j : iJumpChars) { + if (j == c) { + go = true; + break; + } + } + } + + if (!go) { + lastCharacter = c; + buffer.append(c); + } + + return c; + } + + /** + * Returns the next character from the input stream. Handles Unicode decoding. + */ + public int nextChar() throws IOException { + if (missedChar != null) { + // RETURNS THE PREVIOUS PARSED CHAR + c = missedChar.charValue(); + missedChar = null; + + } else { + int read = in.read(); + if (read == -1) + return -1; + + c = (char) read; + + if (c == '\\') { + read = in.read(); + if (read == -1) + return -1; + + char c2 = (char) read; + if (c2 == 'u') { + // DECODE UNICODE CHAR + final StringBuilder buff = new StringBuilder(8); + for (int i = 0; i < 4; ++i) { + read = in.read(); + if (read == -1) + return -1; + + buff.append((char) read); + } + + cursor += 6; + + return (char) Integer.parseInt(buff.toString(), 16); + } else { + // REMEMBER THE CURRENT CHAR TO RETURN NEXT TIME + missedChar = c2; + } + } + } + + cursor++; + + if (c == NEW_LINE) { + ++lineNumber; + columnNumber = 0; + } else + ++columnNumber; + + return (char) c; + } + + public char lastChar() { + return lastCharacter; + } + + public String getValue() { + return value; + } + + public int getLineNumber() { + return lineNumber; + } + + public int getColumnNumber() { + return columnNumber; + } + + public boolean hasNext() throws IOException { + return in.ready(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONWriter.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONWriter.java new file mode 100755 index 00000000000..0f3d30fc252 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OJSONWriter.java @@ -0,0 +1,502 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.io.*; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +@SuppressWarnings("unchecked") +public class OJSONWriter { + private static final String DEF_FORMAT = "rid,type,version,class,attribSameRow,indent:2,dateAsLong"; + private final String format; + private Writer out; + private boolean prettyPrint = false; + private boolean firstAttribute = true; + + public OJSONWriter(final Writer out) { + this(out, DEF_FORMAT); + } + + public OJSONWriter(final Writer out, final String iJsonFormat) { + this.out = out; + this.format = iJsonFormat; + if (iJsonFormat.contains("prettyPrint")) + prettyPrint = true; + } + + public static String writeValue(final Object iValue) throws IOException { + return writeValue(iValue, DEF_FORMAT); + } + + public static String writeValue(Object iValue, final String iFormat) throws IOException { + return writeValue(iValue, iFormat, 0, null); + } + + public static String writeValue(Object iValue, final String iFormat, final int iIndentLevel, OType valueType) throws IOException { + if (iValue == null) + return "null"; + + final StringBuilder buffer = new StringBuilder(64); + + final boolean oldAutoConvertSettings; + + if (iValue instanceof ORecordLazyMultiValue) { + oldAutoConvertSettings = ((ORecordLazyMultiValue) iValue).isAutoConvertToRecord(); + ((ORecordLazyMultiValue) iValue).setAutoConvertToRecord(false); + } else + oldAutoConvertSettings = false; + + if (iValue instanceof Boolean || iValue instanceof Number) + buffer.append(iValue.toString()); + + else if (iValue instanceof OIdentifiable) { + final OIdentifiable linked = (OIdentifiable) iValue; + if (linked.getIdentity().isValid()) { + buffer.append('\"'); + linked.getIdentity().toString(buffer); + buffer.append('\"'); + } else { + if (iFormat != null && iFormat.contains("shallow")) + buffer.append("{}"); + else { + final ORecord rec = linked.getRecord(); + if (rec != null) { + final String embeddedFormat = iFormat != null && iFormat.isEmpty() ? "indent:" + iIndentLevel : iFormat + ",indent:" + iIndentLevel; + buffer.append(rec.toJSON(embeddedFormat)); + } else + buffer.append("null"); + } + } + + } else if (iValue.getClass().isArray()) { + + if (iValue instanceof byte[]) { + buffer.append('\"'); + final byte[] source = (byte[]) iValue; + + if (iFormat != null && iFormat.contains("shallow")) + buffer.append(source.length); + else + buffer.append(OBase64Utils.encodeBytes(source)); + + buffer.append('\"'); + } else { + buffer.append('['); + int size = Array.getLength(iValue); + if (iFormat != null && iFormat.contains("shallow")) + buffer.append(size); + else + for (int i = 0; i0) + buffer.append(","); + buffer.append(writeValue(Array.get(iValue, i), iFormat)); + } + buffer.append(']'); + + } + } else if (iValue instanceof Iterator) + iteratorToJSON((Iterator) iValue, iFormat, buffer); + else if (iValue instanceof Iterable) + iteratorToJSON(((Iterable) iValue).iterator(), iFormat, buffer); + + else if (iValue instanceof Map) + mapToJSON((Map) iValue, iFormat, buffer); + + else if (iValue instanceof Map.Entry) { + final Map.Entry entry = (Entry) iValue; + buffer.append('{'); + buffer.append(writeValue(entry.getKey(), iFormat)); + buffer.append(":"); + if (iFormat.contains("prettyPrint")) + buffer.append(' '); + buffer.append(writeValue(entry.getValue(), iFormat)); + buffer.append('}'); + } else if (iValue instanceof Date) { + if (iFormat.indexOf("dateAsLong")>-1) + buffer.append(((Date) iValue).getTime()); + else { + buffer.append('"'); + buffer.append(ODateHelper.getDateTimeFormatInstance().format(iValue)); + buffer.append('"'); + } + } else if (iValue instanceof BigDecimal) + buffer.append(((BigDecimal) iValue).toPlainString()); + + else if (iValue instanceof ORecordLazyMultiValue) + iteratorToJSON(((ORecordLazyMultiValue) iValue).rawIterator(), iFormat, buffer); + else if (iValue instanceof Iterable) + iteratorToJSON(((Iterable) iValue).iterator(), iFormat, buffer); + + else { + if(valueType == null) + valueType = OType.getTypeByValue(iValue); + + if(valueType == OType.CUSTOM){ + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream object = new ObjectOutputStream(baos); + object.writeObject(iValue); + object.flush(); + buffer.append('"'); + buffer.append(OBase64Utils.encodeBytes(baos.toByteArray())); + buffer.append('"'); + }else { + // TREAT IT AS STRING + final String v = iValue.toString(); + buffer.append('"'); + buffer.append(encode(v)); + buffer.append('"'); + } + } + + if (iValue instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) iValue).setAutoConvertToRecord(oldAutoConvertSettings); + + return buffer.toString(); + } + + protected static void iteratorToJSON(final Iterator it, final String iFormat, final StringBuilder buffer) throws IOException { + buffer.append('['); + if (iFormat != null && iFormat.contains("shallow")) { + if (it instanceof OMultiCollectionIterator) + buffer.append(((OMultiCollectionIterator) it).size()); + else { + // COUNT THE MULTI VALUE + int i; + for (i = 0; it.hasNext(); ++i) + it.next(); + buffer.append(i); + } + } else { + for (int i = 0; it.hasNext(); ++i) { + if (i>0) + buffer.append(","); + buffer.append(writeValue(it.next(), iFormat)); + } + } + buffer.append(']'); + } + + public static Object encode(final Object iValue) { + return OIOUtils.encode(iValue); + } + + public static String listToJSON(final Collection iRecords, final String iFormat) { + try { + final StringWriter buffer = new StringWriter(); + final OJSONWriter json = new OJSONWriter(buffer); + // WRITE RECORDS + json.beginCollection(0, false, null); + if (iRecords != null) { + if (iFormat != null && iFormat.contains("shallow")) { + buffer.append("" + iRecords.size()); + } else { + int counter = 0; + String objectJson; + for (OIdentifiable rec : iRecords) { + if (rec != null) + try { + objectJson = iFormat != null ? rec.getRecord().toJSON(iFormat) : rec.getRecord().toJSON(); + + if (counter++>0) + buffer.append(","); + + buffer.append(objectJson); + } catch (Exception e) { + OLogManager.instance().error(json, "Error transforming record " + rec.getIdentity() + " to JSON", e); + } + } + } + } + json.endCollection(0, false); + + return buffer.toString(); + } catch (IOException e) { + throw OException.wrapException(new OSerializationException("Error on serializing collection"), e); + } + } + + public static String mapToJSON(Map iMap) { + return mapToJSON(iMap, null, new StringBuilder(128)); + } + + public static String mapToJSON(final Map iMap, final String iFormat, final StringBuilder buffer) { + try { + buffer.append('{'); + if (iMap != null) { + int i = 0; + Entry entry; + for (Iterator it = iMap.entrySet().iterator(); it.hasNext(); ++i) { + entry = (Entry) it.next(); + if (i>0) + buffer.append(","); + buffer.append(writeValue(entry.getKey(), iFormat)); + buffer.append(":"); + buffer.append(writeValue(entry.getValue(), iFormat)); + } + } + buffer.append('}'); + return buffer.toString(); + } catch (IOException e) { + throw OException.wrapException(new OSerializationException("Error on serializing map"), e); + } + } + + public OJSONWriter beginObject() throws IOException { + beginObject(0, false, null); + return this; + } + + public OJSONWriter beginObject(final int iIdentLevel) throws IOException { + beginObject(iIdentLevel, false, null); + return this; + } + + public OJSONWriter beginObject(final Object iName) throws IOException { + beginObject(-1, false, iName); + return this; + } + + public OJSONWriter beginObject(final int iIdentLevel, final boolean iNewLine, final Object iName) throws IOException { + if (!firstAttribute) + out.append(","); + + format(iIdentLevel, iNewLine); + + if (iName != null) { + out.append("\"" + iName.toString() + "\":"); + if (prettyPrint) + out.append(' '); + } + + out.append('{'); + + firstAttribute = true; + return this; + } + + public OJSONWriter writeRecord(final int iIdentLevel, final boolean iNewLine, final Object iName, final ORecord iRecord) throws IOException { + if (!firstAttribute) + out.append(","); + + format(iIdentLevel, iNewLine); + + if (iName != null) { + out.append("\"" + iName.toString() + "\":"); + if (prettyPrint) + out.append(' '); + } + + out.append(iRecord.toJSON(format)); + + firstAttribute = false; + return this; + } + + public OJSONWriter endObject() throws IOException { + format(-1, true); + out.append('}'); + return this; + } + + public OJSONWriter endObject(final int iIdentLevel) throws IOException { + return endObject(iIdentLevel, true); + } + + public OJSONWriter endObject(final int iIdentLevel, final boolean iNewLine) throws IOException { + format(iIdentLevel, iNewLine); + out.append('}'); + firstAttribute = false; + return this; + } + + public OJSONWriter beginCollection(final String iName) throws IOException { + return beginCollection(-1, false, iName); + } + + public OJSONWriter beginCollection(final int iIdentLevel, final boolean iNewLine, final String iName) throws IOException { + if (!firstAttribute) + out.append(","); + + format(iIdentLevel, iNewLine); + + if (iName != null && !iName.isEmpty()) { + out.append(writeValue(iName, format)); + out.append(":"); + if (prettyPrint) + out.append(' '); + } + out.append("["); + + firstAttribute = true; + return this; + } + + public OJSONWriter endCollection() throws IOException { + return endCollection(-1, false); + } + + public OJSONWriter endCollection(final int iIdentLevel, final boolean iNewLine) throws IOException { + format(iIdentLevel, iNewLine); + firstAttribute = false; + out.append(']'); + return this; + } + + public OJSONWriter writeObjects(final String iName, Object[]... iPairs) throws IOException { + return writeObjects(-1, false, iName, iPairs); + } + + public OJSONWriter writeObjects(int iIdentLevel, boolean iNewLine, final String iName, Object[]... iPairs) throws IOException { + for (int i = 0; i-1) { + if (iNewLine) + newline(); + + if (prettyPrint) + for (int i = 0; i networkSerializer = new ThreadLocal(); + + public static ORecordSerializer getNetworkSerializer() { + return networkSerializer != null ? networkSerializer.get() : null; + } + + public static void setNetworkSerializer(ORecordSerializer value) { + networkSerializer.set(value); + } + + static { + Orient.instance().registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (networkSerializer == null) + networkSerializer = new ThreadLocal(); + } + + @Override + public void onShutdown() { + networkSerializer = null; + } + }); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OStringSerializerHelper.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OStringSerializerHelper.java new file mode 100755 index 00000000000..52732cba0ec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/OStringSerializerHelper.java @@ -0,0 +1,1101 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OStringParser; +import com.orientechnologies.common.types.OBinary; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringSerializerAnyStreamable; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; + +import java.math.BigDecimal; +import java.util.*; + +public abstract class OStringSerializerHelper { + public static final char RECORD_SEPARATOR = ','; + + public static final String CLASS_SEPARATOR = "@"; + public static final char LINK = ORID.PREFIX; + public static final char EMBEDDED_BEGIN = '('; + public static final char EMBEDDED_END = ')'; + public static final char LIST_BEGIN = '['; + public static final char LIST_END = ']'; + public static final char SET_BEGIN = '<'; + public static final String LINKSET_PREFIX = "" + SET_BEGIN + LINK + CLASS_SEPARATOR; + public static final char SET_END = '>'; + public static final char MAP_BEGIN = '{'; + public static final char MAP_END = '}'; + public static final char BAG_BEGIN = '%'; + public static final char BAG_END = ';'; + public static final char BINARY_BEGINEND = '_'; + public static final char CUSTOM_TYPE = '^'; + public static final char ENTRY_SEPARATOR = ':'; + public static final char PARAMETER_NAMED = ':'; + public static final char PARAMETER_POSITIONAL = '?'; + public static final char[] PARAMETER_SEPARATOR = new char[] { ',' }; + public static final char[] PARAMETER_EXT_SEPARATOR = new char[] { ' ', '.' }; + public static final char[] DEFAULT_IGNORE_CHARS = new char[] { '\n', '\r', ' ' }; + public static final char[] DEFAULT_FIELD_SEPARATOR = new char[] { ',', ' ' }; + public static final char COLLECTION_SEPARATOR = ','; + + public static Object fieldTypeFromStream(final ODocument iDocument, OType iType, final Object iValue) { + if (iValue == null) + return null; + + if (iType == null) + iType = OType.EMBEDDED; + + switch (iType) { + case STRING: + if (iValue instanceof String) { + final String s = (String) iValue; + return decode(OIOUtils.getStringContent(s)); + } + return iValue.toString(); + + case INTEGER: + if (iValue instanceof Integer) + return iValue; + return new Integer(OIOUtils.getStringContent(iValue)); + + case BOOLEAN: + if (iValue instanceof Boolean) + return iValue; + return new Boolean(OIOUtils.getStringContent(iValue)); + + case DECIMAL: + if (iValue instanceof BigDecimal) + return iValue; + return new BigDecimal(OIOUtils.getStringContent(iValue)); + + case FLOAT: + if (iValue instanceof Float) + return iValue; + return new Float(OIOUtils.getStringContent(iValue)); + + case LONG: + if (iValue instanceof Long) + return iValue; + return new Long(OIOUtils.getStringContent(iValue)); + + case DOUBLE: + if (iValue instanceof Double) + return iValue; + return new Double(OIOUtils.getStringContent(iValue)); + + case SHORT: + if (iValue instanceof Short) + return iValue; + return new Short(OIOUtils.getStringContent(iValue)); + + case BYTE: + if (iValue instanceof Byte) + return iValue; + return new Byte(OIOUtils.getStringContent(iValue)); + + case BINARY: + return getBinaryContent(iValue); + + case DATE: + case DATETIME: + if (iValue instanceof Date) + return iValue; + return new Date(Long.parseLong(OIOUtils.getStringContent(iValue))); + + case LINK: + if (iValue instanceof ORID) + return iValue.toString(); + else if (iValue instanceof String) + return new ORecordId((String) iValue); + else + return ((ORecord) iValue).getIdentity().toString(); + + case EMBEDDED: + // EMBEDDED + return OStringSerializerAnyStreamable.INSTANCE.fromStream((String) iValue); + + case EMBEDDEDMAP: + // RECORD + final String value = (String) iValue; + return ORecordSerializerSchemaAware2CSV.INSTANCE.embeddedMapFromStream(iDocument, null, value, null); + + case ANY: + if (iValue instanceof String) { + final String s = (String) iValue; + return decode(OIOUtils.getStringContent(s)); + } + return iValue; + } + + throw new IllegalArgumentException("Type " + iType + " does not support converting value: " + iValue); + } + + public static String smartTrim(String source, final boolean removeLeadingSpaces, final boolean removeTailingSpaces) { + int startIndex = 0; + int length = source.length(); + + while (startIndex < length && source.charAt(startIndex) == ' ') { + startIndex++; + } + + if (!removeLeadingSpaces && startIndex > 0) + startIndex--; + + while (length > startIndex && source.charAt(length - 1) == ' ') { + length--; + } + + if (!removeTailingSpaces && length < source.length()) + length++; + + return source.substring(startIndex, length); + } + + public static List smartSplit(final String iSource, final char iRecordSeparator, boolean iPreserveQuotes, + final char... iJumpChars) { + return smartSplit(iSource, new char[] { iRecordSeparator }, 0, -1, true, true, false, false, true, iPreserveQuotes, iJumpChars); + } + + public static List smartSplit(final String iSource, final char iRecordSeparator, final char... iJumpChars) { + return smartSplit(iSource, new char[] { iRecordSeparator }, 0, -1, true, true, false, false, iJumpChars); + } + + public static List smartSplit(final String iSource, final char iRecordSeparator, final boolean iConsiderSets, + boolean considerBags, final char... iJumpChars) { + return smartSplit(iSource, new char[] { iRecordSeparator }, 0, -1, false, true, iConsiderSets, considerBags, iJumpChars); + } + + public static List smartSplit(final String iSource, final char[] iRecordSeparator, int beginIndex, final int endIndex, + final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final boolean iConsiderBags, final char... iJumpChars) { + return smartSplit(iSource, iRecordSeparator, beginIndex, endIndex, iStringSeparatorExtended, iConsiderBraces, iConsiderSets, + iConsiderBags, true, iJumpChars); + } + + public static List smartSplit(final String iSource, final char[] iRecordSeparator, int beginIndex, final int endIndex, + final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final boolean iConsiderBags, boolean iUnicode, final char... iJumpChars) { + return smartSplit(iSource, iRecordSeparator, beginIndex, endIndex, iStringSeparatorExtended, iConsiderBraces, iConsiderSets, + iConsiderBags, iUnicode, false, iJumpChars); + + } + + public static List smartSplit(final String iSource, final char[] iRecordSeparator, int beginIndex, final int endIndex, + final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final boolean iConsiderBags, boolean iUnicode, boolean iPreserveQuotes, final char... iJumpChars) { + + final StringBuilder buffer = new StringBuilder(128); + final ArrayList parts = new ArrayList(); + + if (iSource != null && !iSource.isEmpty()) { + final char[] source = iSource.toCharArray(); + + while ((beginIndex = parse(source, buffer, beginIndex, endIndex, iRecordSeparator, iStringSeparatorExtended, iConsiderBraces, + iConsiderSets, -1, iConsiderBags, iUnicode, iPreserveQuotes, iJumpChars)) > -1) { + parts.add(buffer.toString()); + buffer.setLength(0); + } + + if (buffer.length() > 0 || isCharPresent(iSource.charAt(iSource.length() - 1), iRecordSeparator)) + parts.add(buffer.toString()); + } + + return parts; + } + + public static List smartSplit(final String iSource, final char[] iRecordSeparator, + final boolean[] iRecordSeparatorIncludeAsPrefix, final boolean[] iRecordSeparatorIncludeAsPostfix, int beginIndex, + final int endIndex, final boolean iStringSeparatorExtended, boolean iConsiderBraces, boolean iConsiderSets, + boolean considerBags, final char... iJumpChars) { + final StringBuilder buffer = new StringBuilder(128); + final ArrayList parts = new ArrayList(); + + int startSeparatorAt = -1; + if (iSource != null && !iSource.isEmpty()) { + final char[] source = iSource.toCharArray(); + + while ((beginIndex = parse(source, buffer, beginIndex, endIndex, iRecordSeparator, iStringSeparatorExtended, iConsiderBraces, + iConsiderSets, startSeparatorAt, considerBags, true, iJumpChars)) > -1) { + + if (beginIndex > -1) { + final char lastSeparator = source[beginIndex - 1]; + for (int i = 0; i < iRecordSeparator.length; ++i) + if (iRecordSeparator[i] == lastSeparator) { + if (iRecordSeparatorIncludeAsPrefix[i]) { + buffer.append(lastSeparator); + } + break; + } + } + + if (buffer.length() > 0) { + parts.add(buffer.toString()); + buffer.setLength(0); + } + + startSeparatorAt = beginIndex; + + if (beginIndex > -1) { + final char lastSeparator = source[beginIndex - 1]; + for (int i = 0; i < iRecordSeparator.length; ++i) + if (iRecordSeparator[i] == lastSeparator) { + if (iRecordSeparatorIncludeAsPostfix[i]) { + beginIndex--; + startSeparatorAt = beginIndex + 1; + } + break; + } + } + } + + if (buffer.length() > 0) + parts.add(buffer.toString()); + } + + return parts; + } + + public static int parse(final String iSource, final StringBuilder iBuffer, final int beginIndex, final int endIndex, + final char[] iSeparator, final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final int iMinPosSeparatorAreValid, boolean considerBags, final char... iJumpChars) { + return parse(iSource.toCharArray(), iBuffer, beginIndex, endIndex, iSeparator, iStringSeparatorExtended, iConsiderBraces, + iConsiderSets, iMinPosSeparatorAreValid, considerBags, true, false, iJumpChars); + } + + public static int parse(final String iSource, final StringBuilder iBuffer, final int beginIndex, final int endIndex, + final char[] iSeparator, final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final int iMinPosSeparatorAreValid, boolean considerBags, boolean iPreserveQuotes, final char... iJumpChars) { + return parse(iSource.toCharArray(), iBuffer, beginIndex, endIndex, iSeparator, iStringSeparatorExtended, iConsiderBraces, + iConsiderSets, iMinPosSeparatorAreValid, considerBags, true, iPreserveQuotes, iJumpChars); + } + + public static int parse(final char[] iSource, final StringBuilder iBuffer, final int beginIndex, final int endIndex, + final char[] iSeparator, final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final int iMinPosSeparatorAreValid, boolean considerBags, final boolean iUnicode, final char... iJumpChars) { + return parse(iSource, iBuffer, beginIndex, endIndex, iSeparator, iStringSeparatorExtended, iConsiderBraces, iConsiderSets, + iMinPosSeparatorAreValid, considerBags, iUnicode, false, iJumpChars); + + } + + public static int parse(final char[] iSource, final StringBuilder iBuffer, final int beginIndex, final int endIndex, + final char[] iSeparator, final boolean iStringSeparatorExtended, final boolean iConsiderBraces, final boolean iConsiderSets, + final int iMinPosSeparatorAreValid, boolean considerBags, final boolean iUnicode, boolean iPreserveQuotes, + final char... iJumpChars) { + if (beginIndex < 0) + return beginIndex; + + char stringBeginChar = ' '; + boolean encodeMode = false; + int insideParenthesis = 0; + int insideList = 0; + int insideSet = 0; + int insideMap = 0; + int insideLinkPart = 0; + int insideBag = 0; + + final int max = endIndex > -1 ? endIndex + 1 : iSource.length; + + iBuffer.ensureCapacity(max); + + // JUMP FIRST CHARS + int i = beginIndex; + for (; i < max; ++i) { + final char c = iSource[i]; + if (!isCharPresent(c, iJumpChars)) + break; + } + + for (; i < max; ++i) { + final char c = iSource[i]; + + if (stringBeginChar == ' ') { + // OUTSIDE A STRING + + if (iConsiderBraces) { + if (c == LIST_BEGIN) { + if (i < iMinPosSeparatorAreValid || insideParenthesis > 0 || insideList > 0 || !isCharPresent(c, iSeparator)) + insideList++; + } else if (c == LIST_END) { + if (i < iMinPosSeparatorAreValid || insideParenthesis > 0 || insideList > 0 || !isCharPresent(c, iSeparator)) { + if (insideList == 0) + throw new OSerializationException( + "Found invalid " + LIST_END + " character at position " + i + " of text " + new String(iSource) + + ". Ensure it is opened and closed correctly."); + insideList--; + } + } else if (c == EMBEDDED_BEGIN) { + insideParenthesis++; + } else if (c == EMBEDDED_END) { + // if (!isCharPresent(c, iRecordSeparator)) { + if (insideParenthesis == 0) + throw new OSerializationException( + "Found invalid " + EMBEDDED_END + " character at position " + i + " of text " + new String(iSource) + + ". Ensure it is opened and closed correctly."); + // } + insideParenthesis--; + + } else if (c == MAP_BEGIN) { + insideMap++; + } else if (c == MAP_END) { + if (i < iMinPosSeparatorAreValid || !isCharPresent(c, iSeparator)) { + if (insideMap == 0) + throw new OSerializationException( + "Found invalid " + MAP_END + " character at position " + i + " of text " + new String(iSource) + + ". Ensure it is opened and closed correctly."); + insideMap--; + } + } else if (c == LINK) + // FIRST PART OF LINK + insideLinkPart = 1; + else if (insideLinkPart == 1 && c == ORID.SEPARATOR) + // SECOND PART OF LINK + insideLinkPart = 2; + else { + if (iConsiderSets) + if (c == SET_BEGIN) + insideSet++; + else if (c == SET_END) { + if (i < iMinPosSeparatorAreValid || !isCharPresent(c, iSeparator)) { + if (insideSet == 0) + throw new OSerializationException( + "Found invalid " + SET_END + " character at position " + i + " of text " + new String(iSource) + + ". Ensure it is opened and closed correctly."); + insideSet--; + } + } + if (considerBags) { + if (c == BAG_BEGIN) + insideBag++; + else if (c == BAG_END) + if (!isCharPresent(c, iSeparator)) { + if (insideBag == 0) + throw new OSerializationException( + "Found invalid " + BAG_BEGIN + " character. Ensure it is opened and closed correctly."); + insideBag--; + } + } + } + } + + if (insideLinkPart > 0 && c != '-' && !Character.isDigit(c) && c != ORID.SEPARATOR && c != LINK) + insideLinkPart = 0; + + if ((c == '"' || c == '`' || iStringSeparatorExtended && c == '\'') && !encodeMode) { + // START STRING + stringBeginChar = c; + } + + if (insideParenthesis == 0 && insideList == 0 && insideSet == 0 && insideMap == 0 && insideLinkPart == 0 + && insideBag == 0) { + // OUTSIDE A PARAMS/COLLECTION/MAP + if (i >= iMinPosSeparatorAreValid && isCharPresent(c, iSeparator)) { + // SEPARATOR (OUTSIDE A STRING): PUSH + return i + 1; + } + } + + if (iJumpChars.length > 0) + if (i >= iMinPosSeparatorAreValid && isCharPresent(c, iJumpChars)) + continue; + } else { + // INSIDE A STRING + if ((c == '"' || c == '`' || iStringSeparatorExtended && c == '\'') && !encodeMode) { + // CLOSE THE STRING ? + if (stringBeginChar == c) { + // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH + stringBeginChar = ' '; + } + } + } + + if (c == '\\' && !encodeMode && !iPreserveQuotes) { + // ESCAPE CHARS + final char nextChar = iSource[i + 1]; + if (nextChar == 'u' && iUnicode) { + i = OStringParser.readUnicode(iSource, i + 2, iBuffer); + continue; + } else if (nextChar == 'n') { + iBuffer.append("\n"); + i++; + continue; + } else if (nextChar == 'r') { + iBuffer.append("\r"); + i++; + continue; + } else if (nextChar == 't') { + iBuffer.append("\t"); + i++; + continue; + } else if (nextChar == 'f') { + iBuffer.append("\f"); + i++; + continue; + } else + encodeMode = true; + } else + encodeMode = false; + + if (c != '\\' && encodeMode) { + encodeMode = false; + } + + iBuffer.append(c); + } + return -1; + } + + public static boolean isCharPresent(final char iCharacter, final char[] iCharacters) { + final int len = iCharacters.length; + for (int i = 0; i < len; ++i) { + if (iCharacter == iCharacters[i]) { + return true; + } + } + + return false; + } + + public static List split(final String iSource, final char iRecordSeparator, final char... iJumpCharacters) { + return split(iSource, 0, iSource.length(), iRecordSeparator, iJumpCharacters); + } + + public static Collection split(final Collection iParts, final String iSource, final char iRecordSeparator, + final char... iJumpCharacters) { + return split(iParts, iSource, 0, iSource.length(), iRecordSeparator, iJumpCharacters); + } + + public static List split(final String iSource, final int iStartPosition, final int iEndPosition, + final char iRecordSeparator, final char... iJumpCharacters) { + return (List) split(new ArrayList(), iSource, iStartPosition, iSource.length(), iRecordSeparator, + iJumpCharacters); + } + + public static Collection split(final Collection iParts, final String iSource, final int iStartPosition, + final int iEndPosition, final char iRecordSeparator, final char... iJumpCharacters) { + return split(iParts, iSource, iStartPosition, iEndPosition, String.valueOf(iRecordSeparator), iJumpCharacters); + } + + public static Collection split(final Collection iParts, final String iSource, final int iStartPosition, + int iEndPosition, final String iRecordSeparators, final char... iJumpCharacters) { + if (iEndPosition == -1) + iEndPosition = iSource.length(); + + final StringBuilder buffer = new StringBuilder(128); + + for (int i = iStartPosition; i < iEndPosition; ++i) { + char c = iSource.charAt(i); + + if (iRecordSeparators.indexOf(c) > -1) { + iParts.add(buffer.toString()); + buffer.setLength(0); + } else { + if (iJumpCharacters.length > 0 && buffer.length() == 0) { + // CHECK IF IT'S A CHAR TO JUMP + if (!isCharPresent(c, iJumpCharacters)) { + buffer.append(c); + } + } else + buffer.append(c); + } + } + + if (iJumpCharacters.length > 0 && buffer.length() > 0) { + // CHECK THE END OF LAST ITEM IF NEED TO CUT THE CHARS TO JUMP + char b; + int newSize = 0; + boolean found; + for (int i = buffer.length() - 1; i >= 0; --i) { + b = buffer.charAt(i); + found = false; + for (char j : iJumpCharacters) { + if (j == b) { + found = true; + ++newSize; + break; + } + } + if (!found) + break; + } + if (newSize > 0) + buffer.setLength(buffer.length() - newSize); + } + + iParts.add(buffer.toString()); + + return iParts; + } + + public static String joinIntArray(int[] iArray) { + final StringBuilder ids = new StringBuilder(iArray.length * 3); + for (int id : iArray) { + if (ids.length() > 0) + ids.append(RECORD_SEPARATOR); + ids.append(id); + } + return ids.toString(); + } + + public static int[] splitIntArray(final String iInput) { + final List items = split(iInput, RECORD_SEPARATOR); + final int[] values = new int[items.size()]; + for (int i = 0; i < items.size(); ++i) { + values[i] = Integer.parseInt(items.get(i).trim()); + } + return values; + } + + public static boolean contains(final String iText, final char iSeparator) { + if (iText == null) + return false; + + return iText.indexOf(iSeparator) > -1; + } + + public static int getCollection(final String iText, final int iStartPosition, final Collection iCollection) { + return getCollection(iText, iStartPosition, iCollection, LIST_BEGIN, LIST_END, COLLECTION_SEPARATOR); + } + + public static int getCollection(final String iText, final int iStartPosition, final Collection iCollection, + final char iCollectionBegin, final char iCollectionEnd, final char iCollectionSeparator) { + int openPos = iText.indexOf(iCollectionBegin, iStartPosition); + if (openPos == -1) + return -1; + + final StringBuilder buffer = new StringBuilder(128); + + boolean escape = false; + char insideQuote = ' '; + int currentPos, deep; + int maxPos = iText.length() - 1; + for (currentPos = openPos + 1, deep = 1; deep > 0; currentPos++) { + if (currentPos > maxPos) + return -1; + + char c = iText.charAt(currentPos); + + if (buffer.length() == 0 && c == ' ') + continue; + + if (c == iCollectionBegin) { + // BEGIN + buffer.append(c); + deep++; + } else if (c == iCollectionEnd) { + // END + if (deep > 1) + buffer.append(c); + deep--; + } else if (c == iCollectionSeparator) { + // SEPARATOR + if (deep > 1 || insideQuote != ' ') { + buffer.append(c); + } else { + iCollection.add(buffer.toString().trim()); + buffer.setLength(0); + } + } else if (!escape && ((insideQuote == ' ' && (c == '"' || c == '\'')) || (insideQuote == c))) { + insideQuote = insideQuote == ' ' ? c : ' '; + buffer.append(c); + } else { + // COLLECT + if (!escape && c == '\\' && (currentPos + 1 <= maxPos)) { + // ESCAPE CHARS + final char nextChar = iText.charAt(currentPos + 1); + + if (nextChar == 'u') { + currentPos = OStringParser.readUnicode(iText, currentPos + 2, buffer); + } else if (nextChar == 'n') { + buffer.append("\n"); + currentPos++; + } else if (nextChar == 'r') { + buffer.append("\r"); + currentPos++; + } else if (nextChar == 't') { + buffer.append("\t"); + currentPos++; + } else if (nextChar == 'f') { + buffer.append("\f"); + currentPos++; + } else + escape = true; + + continue; + } + escape = false; + buffer.append(c); + } + } + + if (buffer.length() > 0) + iCollection.add(buffer.toString().trim()); + + return --currentPos; + } + + public static int getParameters(final String iText, final int iBeginPosition, int iEndPosition, final List iParameters) { + iParameters.clear(); + + final int openPos = iText.indexOf(EMBEDDED_BEGIN, iBeginPosition); + if (openPos == -1 || (iEndPosition > -1 && openPos > iEndPosition)) + return iBeginPosition; + + final StringBuilder buffer = new StringBuilder(128); + parse(iText, buffer, openPos, iEndPosition, PARAMETER_EXT_SEPARATOR, true, true, false, -1, false); + if (buffer.length() == 0) + return iBeginPosition; + + final String t = buffer.substring(1, buffer.length() - 1).trim(); + final List pars = smartSplit(t, PARAMETER_SEPARATOR, 0, -1, true, true, false, false); + + for (int i = 0; i < pars.size(); ++i) + iParameters.add(pars.get(i).trim()); + + return iBeginPosition + buffer.length(); + } + + public static int getEmbedded(final String iText, final int iBeginPosition, int iEndPosition, final StringBuilder iEmbedded) { + final int openPos = iText.indexOf(EMBEDDED_BEGIN, iBeginPosition); + if (openPos == -1 || (iEndPosition > -1 && openPos > iEndPosition)) + return iBeginPosition; + + final StringBuilder buffer = new StringBuilder(128); + parse(iText, buffer, openPos, iEndPosition, PARAMETER_EXT_SEPARATOR, true, true, false, -1, false); + if (buffer.length() == 0) + return iBeginPosition; + + final String t = buffer.substring(1, buffer.length() - 1).trim(); + iEmbedded.append(t); + return iBeginPosition + buffer.length(); + } + + public static List getParameters(final String iText) { + final List params = new ArrayList(); + try { + getParameters(iText, 0, -1, params); + } catch (Exception e) { + throw OException.wrapException(new OCommandSQLParsingException("Error on reading parameters in: " + iText), e); + } + return params; + } + + public static Map getMap(final String iText) { + int openPos = iText.indexOf(MAP_BEGIN); + if (openPos == -1) + return Collections.emptyMap(); + + int closePos = iText.indexOf(MAP_END, openPos + 1); + if (closePos == -1) + return Collections.emptyMap(); + + final List entries = smartSplit(iText.substring(openPos + 1, closePos), COLLECTION_SEPARATOR); + if (entries.size() == 0) + return Collections.emptyMap(); + + Map map = new HashMap(); + + List entry; + for (String item : entries) { + if (item != null && !item.isEmpty()) { + entry = OStringSerializerHelper.split(item, OStringSerializerHelper.ENTRY_SEPARATOR); + + final String key = entry.get(0).trim(); + final String value = entry.get(1).trim(); + + map.put((String) fieldTypeFromStream(null, OType.STRING, key), value); + } + } + + return map; + } + + /** + * Transforms, only if needed, the source string escaping the characters \ and ". + * + * @param iText Input String + * @return Modified string if needed, otherwise the same input object + * @see OStringSerializerHelper#decode(String) + */ + public static String encode(final String iText) { + int pos = -1; + + final int newSize = iText.length(); + for (int i = 0; i < newSize; ++i) { + final char c = iText.charAt(i); + + if (c == '"' || c == '\\') { + pos = i; + break; + } + } + + if (pos > -1) { + // CHANGE THE INPUT STRING + final StringBuilder iOutput = new StringBuilder((int) ((float) newSize * 1.5f)); + + char c; + for (int i = 0; i < newSize; ++i) { + c = iText.charAt(i); + + if (c == '"' || c == '\\') + iOutput.append('\\'); + + iOutput.append(c); + } + return iOutput.toString(); + } + + return iText; + } + + /** + * Transforms, only if needed, the source string un-escaping the characters \ and ". + * + * @param iText Input String + * @return Modified string if needed, otherwise the same input object + * @see OStringSerializerHelper#encode(String) + */ + public static String decode(final String iText) { + int pos = -1; + + final int textSize = iText.length(); + for (int i = 0; i < textSize; ++i) + if (iText.charAt(i) == '"' || iText.charAt(i) == '\\') { + pos = i; + break; + } + + if (pos == -1) + // NOT FOUND, RETURN THE SAME STRING (AVOID COPIES) + return iText; + + // CHANGE THE INPUT STRING + final StringBuilder buffer = new StringBuilder(textSize); + buffer.append(iText.substring(0, pos)); + + boolean escaped = false; + for (int i = pos; i < textSize; ++i) { + final char c = iText.charAt(i); + + boolean wasEscaped = escaped; + if (escaped) + escaped = false; + else if (c == '\\') { + escaped = true; + continue; + } + + if (wasEscaped) { + if (c == 'n') { + buffer.append("\n"); + } else if (c == 't') { + buffer.append("\t"); + } else if (c == 'r') { + buffer.append("\r"); + } else if (c == 'b') { + buffer.append("\b"); + } else if (c == 'f') { + buffer.append("\f"); + } else { + buffer.append(c); + } + } else { + buffer.append(c); + } + } + + return buffer.toString(); + } + + public static OClass getRecordClassName(final String iValue, OClass iLinkedClass) { + // EXTRACT THE CLASS NAME + final int classSeparatorPos = OStringParser + .indexOfOutsideStrings(iValue, OStringSerializerHelper.CLASS_SEPARATOR.charAt(0), 0, -1); + if (classSeparatorPos > -1) { + final String className = iValue.substring(0, classSeparatorPos); + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (className != null && database != null) + iLinkedClass = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(className); + } + return iLinkedClass; + } + + /** + * Use OIOUtils.getStringContent(iValue) instead. + * + * @param iValue + * @return + */ + @Deprecated public static String getStringContent(final Object iValue) { + // MOVED + return OIOUtils.getStringContent(iValue); + } + + /** + * Returns the binary representation of a content. If it's a String a Base64 decoding is applied. + */ + public static byte[] getBinaryContent(final Object iValue) { + if (iValue == null) + return null; + else if (iValue instanceof OBinary) + return ((OBinary) iValue).toByteArray(); + else if (iValue instanceof byte[]) + return (byte[]) iValue; + else if (iValue instanceof String) { + String s = (String) iValue; + if (s.length() > 1 && (s.charAt(0) == BINARY_BEGINEND && s.charAt(s.length() - 1) == BINARY_BEGINEND) || (s.charAt(0) == '\'' + && s.charAt(s.length() - 1) == '\'')) + // @COMPATIBILITY 1.0rc7-SNAPSHOT ' TO SUPPORT OLD DATABASES + s = s.substring(1, s.length() - 1); + // IN CASE OF JSON BINARY IMPORT THIS EXEPTION IS WRONG + // else + // throw new IllegalArgumentException("Not binary type: " + iValue); + + return OBase64Utils.decode(s); + } else + throw new IllegalArgumentException( + "Cannot parse binary as the same type as the value (class=" + iValue.getClass().getName() + "): " + iValue); + } + + /** + * Checks if a string contains alphanumeric only characters. + * + * @param iContent String to check + * @return true is all the content is alphanumeric, otherwise false + */ + public static boolean isAlphanumeric(final String iContent) { + final int tot = iContent.length(); + for (int i = 0; i < tot; ++i) { + if (!Character.isLetterOrDigit(iContent.charAt(i))) + return false; + } + return true; + } + + public static String removeQuotationMarks(final String iValue) { + if (iValue != null && iValue.length() > 1 && (iValue.charAt(0) == '\'' && iValue.charAt(iValue.length() - 1) == '\'' + || iValue.charAt(0) == '"' && iValue.charAt(iValue.length() - 1) == '"')) + return iValue.substring(1, iValue.length() - 1); + + return iValue; + } + + public static boolean startsWithIgnoreCase(final String iFirst, final String iSecond) { + if (iFirst == null) + throw new IllegalArgumentException("Origin string to compare is null"); + if (iSecond == null) + throw new IllegalArgumentException("String to match is null"); + + final int iSecondLength = iSecond.length(); + + if (iSecondLength > iFirst.length()) + return false; + + for (int i = 0; i < iSecondLength; ++i) { + if (Character.toUpperCase(iFirst.charAt(i)) != Character.toUpperCase(iSecond.charAt(i))) + return false; + } + return true; + } + + public static int indexOf(final String iSource, final int iBegin, char... iChars) { + if (iChars.length == 1) + // ONE CHAR: USE JAVA INDEXOF + return iSource.indexOf(iChars[0], iBegin); + + final int len = iSource.length(); + for (int i = iBegin; i < len; ++i) { + for (int k = 0; k < iChars.length; ++k) { + final char c = iSource.charAt(i); + if (c == iChars[k]) + return i; + } + } + return -1; + } + + /** + * Finds the end of a block delimited by 2 chars. + */ + public static final int findEndBlock(final String iOrigin, final char iBeginChar, final char iEndChar, final int iBeginOffset) { + int inc = 0; + + for (int i = iBeginOffset; i < iOrigin.length(); i++) { + char c = iOrigin.charAt(i); + if (c == '\'') { + // skip to text end + int tend = i; + while (true) { + tend = iOrigin.indexOf('\'', tend + 1); + if (tend < 0) { + throw new OCommandSQLParsingException("Could not find end of text area", iOrigin, i); + } + + if (iOrigin.charAt(tend - 1) == '\\') { + // inner quote, skip it + continue; + } else { + break; + } + } + i = tend; + continue; + } + + if (c != iBeginChar && c != iEndChar) + continue; + + if (c == iBeginChar) { + inc++; + } else if (c == iEndChar) { + inc--; + if (inc == 0) { + return i; + } + } + } + + return -1; + } + + public static int getLowerIndexOf(final String iText, final int iBeginOffset, final String... iToSearch) { + int lowest = -1; + for (String toSearch : iToSearch) { + boolean singleQuote = false; + boolean doubleQuote = false; + boolean backslash = false; + for (int i = iBeginOffset; i < iText.length(); i++) { + if (lowest == -1 || i < lowest) { + if (backslash && (iText.charAt(i) == '\'' || iText.charAt(i) == '"')) { + backslash = false; + continue; + } + if (iText.charAt(i) == '\\') { + backslash = true; + continue; + } + if (iText.charAt(i) == '\'' && !doubleQuote) { + singleQuote = !singleQuote; + continue; + } + if (iText.charAt(i) == '"' && !singleQuote) { + singleQuote = !singleQuote; + continue; + } + + if (!singleQuote && !doubleQuote && iText.startsWith(toSearch, i)) { + lowest = i; + } + } + } + } + + // for (String toSearch : iToSearch) { + // int index = iText.indexOf(toSearch, iBeginOffset); + // if (index > -1 && (lowest == -1 || index < lowest)) + // lowest = index; + // } + return lowest; + } + + public static int getLowerIndexOfKeywords(final String iText, final int iBeginOffset, final String... iToSearch) { + Character lastQuote = null; + List nestedStack = new LinkedList(); + + for (int i = iBeginOffset; i < iText.length(); i++) { + + char prevChar = i < 1 ? '\n' : iText.charAt(i - 1); + char lastChar = iText.charAt(i); + if (lastQuote != null) { + if (lastQuote.equals(lastChar)) { + lastQuote = null; + } + continue; + } + if (lastChar == '\'' || lastChar == '"') { + lastQuote = lastChar; + continue; + } + + if (lastChar == '(' || lastChar == '[' || lastChar == '{') { + nestedStack.add(0, lastChar); + continue; + } + + if (nestedStack.size() > 0) { + Character stackTop = nestedStack.get(0); + + if (lastChar == ')' && stackTop == '(') { + nestedStack.remove(0); + } + if (lastChar == ']' && stackTop == '[') { + nestedStack.remove(0); + } + if (lastChar == '}' && stackTop == '{') { + nestedStack.remove(0); + } + continue; + } + + if (prevChar == ' ' || prevChar == '\n' || prevChar == '\t') { + for (String s : iToSearch) { + if (iText.length() < i + s.length()) { + continue; + } + + if (iText.substring(i, i + s.length()).equalsIgnoreCase(s)) { + if (iText.length() == (i + s.length())) { + return i; + } + char nextChar = iText.charAt(i + s.length()); + if (nextChar == ' ' || nextChar == '\n' || nextChar == '\t') { + return i; + } + } + } + } + } + + return -1; + } + + public static int getHigherIndexOf(final String iText, final int iBeginOffset, final String... iToSearch) { + int lowest = -1; + for (String toSearch : iToSearch) { + int index = iText.indexOf(toSearch, iBeginOffset); + if (index > -1 && (lowest == -1 || index > lowest)) + lowest = index; + } + return lowest; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/OBinarySerializerFactory.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/OBinarySerializerFactory.java new file mode 100755 index 00000000000..34da2b1f061 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/OBinarySerializerFactory.java @@ -0,0 +1,145 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.binary; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.serialization.types.*; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.index.OCompositeKeySerializer; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.index.OSimpleKeySerializer; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerRID; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerSBTreeIndexRIDContainer; + +/** + * This class is responsible for obtaining OBinarySerializer realization, by it's id of type of object that should be serialized. + * + * + * @author Evgeniy Degtiarenko (gmandnepr-at-gmail.com) + */ +public class OBinarySerializerFactory { + + /** + * Size of the type identifier block size + */ + public static final int TYPE_IDENTIFIER_SIZE = 1; + private final ConcurrentMap> serializerIdMap = new ConcurrentHashMap>(); + private final ConcurrentMap> serializerClassesIdMap = new ConcurrentHashMap>(); + private final ConcurrentMap> serializerTypeMap = new ConcurrentHashMap>(); + + private OBinarySerializerFactory() { + } + + public static OBinarySerializerFactory create(int binaryFormatVersion) { + final OBinarySerializerFactory factory = new OBinarySerializerFactory(); + + // STATELESS SERIALIER + factory.registerSerializer(new ONullSerializer(), null); + + factory.registerSerializer(OBooleanSerializer.INSTANCE, OType.BOOLEAN); + factory.registerSerializer(OIntegerSerializer.INSTANCE, OType.INTEGER); + factory.registerSerializer(OShortSerializer.INSTANCE, OType.SHORT); + factory.registerSerializer(OLongSerializer.INSTANCE, OType.LONG); + factory.registerSerializer(OFloatSerializer.INSTANCE, OType.FLOAT); + factory.registerSerializer(ODoubleSerializer.INSTANCE, OType.DOUBLE); + factory.registerSerializer(ODateTimeSerializer.INSTANCE, OType.DATETIME); + factory.registerSerializer(OCharSerializer.INSTANCE, null); + factory.registerSerializer(OStringSerializer.INSTANCE, OType.STRING); + + factory.registerSerializer(OByteSerializer.INSTANCE, OType.BYTE); + factory.registerSerializer(ODateSerializer.INSTANCE, OType.DATE); + factory.registerSerializer(OLinkSerializer.INSTANCE, OType.LINK); + factory.registerSerializer(OCompositeKeySerializer.INSTANCE, null); + factory.registerSerializer(OStreamSerializerRID.INSTANCE, null); + factory.registerSerializer(OBinaryTypeSerializer.INSTANCE, OType.BINARY); + factory.registerSerializer(ODecimalSerializer.INSTANCE, OType.DECIMAL); + + factory.registerSerializer(OStreamSerializerSBTreeIndexRIDContainer.INSTANCE, null); + + // STATEFUL SERIALIER + factory.registerSerializer(OSimpleKeySerializer.ID, OSimpleKeySerializer.class); + + return factory; + } + + public static OBinarySerializerFactory getInstance() { + final ODatabaseDocumentInternal database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (database != null) + return database.getSerializerFactory(); + else + return OBinarySerializerFactory.create(Integer.MAX_VALUE); + } + + public void registerSerializer(final OBinarySerializer iInstance, final OType iType) { + if (serializerIdMap.containsKey(iInstance.getId())) + throw new IllegalArgumentException("Binary serializer with id " + iInstance.getId() + " has been already registered."); + + serializerIdMap.put(iInstance.getId(), iInstance); + if (iType != null) + serializerTypeMap.put(iType, iInstance); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void registerSerializer(final byte iId, final Class iClass) { + if (serializerClassesIdMap.containsKey(iId)) + throw new IllegalStateException("Serializer with id " + iId + " has been already registered."); + + serializerClassesIdMap.put(iId, (Class) iClass); + } + + /** + * Obtain OBinarySerializer instance by it's id. + * + * @param identifier + * is serializes identifier. + * @return OBinarySerializer instance. + */ + public OBinarySerializer getObjectSerializer(final byte identifier) { + OBinarySerializer impl = serializerIdMap.get(identifier); + if (impl == null) { + final Class cls = serializerClassesIdMap.get(identifier); + if (cls != null) + try { + impl = cls.newInstance(); + } catch (Exception e) { + OLogManager.instance().error(this, "Cannot create an instance of class %s invoking the empty constructor", cls); + } + } + return impl; + } + + /** + * Obtain OBinarySerializer realization for the OType + * + * @param type + * is the OType to obtain serializer algorithm for + * @return OBinarySerializer instance + */ + @SuppressWarnings("unchecked") + public OBinarySerializer getObjectSerializer(final OType type) { + return (OBinarySerializer) serializerTypeMap.get(type); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/OLinkSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/OLinkSerializer.java new file mode 100644 index 00000000000..83b6cf17f85 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/OLinkSerializer.java @@ -0,0 +1,168 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.binary.impl; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.common.serialization.types.OShortSerializer; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +import static com.orientechnologies.orient.core.serialization.OBinaryProtocol.bytes2long; +import static com.orientechnologies.orient.core.serialization.OBinaryProtocol.bytes2short; +import static com.orientechnologies.orient.core.serialization.OBinaryProtocol.long2bytes; +import static com.orientechnologies.orient.core.serialization.OBinaryProtocol.short2bytes; + +/** + * Serializer for {@link com.orientechnologies.orient.core.metadata.schema.OType#LINK} + * + * @author Ilya Bershadskiy (ibersh20-at-gmail.com) + * @since 07.02.12 + */ +public class OLinkSerializer implements OBinarySerializer { + public static final byte ID = 9; + private static final int CLUSTER_POS_SIZE = OLongSerializer.LONG_SIZE; + public static final int RID_SIZE = OShortSerializer.SHORT_SIZE + CLUSTER_POS_SIZE; + public static final OLinkSerializer INSTANCE = new OLinkSerializer(); + + public int getObjectSize(final OIdentifiable rid, Object... hints) { + return RID_SIZE; + } + + public void serialize(final OIdentifiable rid, final byte[] stream, final int startPosition, Object... hints) { + final ORID r = rid.getIdentity(); + short2bytes((short) r.getClusterId(), stream, startPosition); + long2bytes(r.getClusterPosition(), stream, startPosition + OShortSerializer.SHORT_SIZE); + } + + public ORecordId deserialize(final byte[] stream, final int startPosition) { + return new ORecordId(bytes2short(stream, startPosition), bytes2long(stream, startPosition + OShortSerializer.SHORT_SIZE)); + } + + public int getObjectSize(final byte[] stream, final int startPosition) { + return RID_SIZE; + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return RID_SIZE; + } + + public void serializeNativeObject(OIdentifiable rid, byte[] stream, int startPosition, Object... hints) { + final ORID r = rid.getIdentity(); + + OShortSerializer.INSTANCE.serializeNative((short) r.getClusterId(), stream, startPosition); + // Wrong implementation but needed for binary compatibility should be used serializeNative + OLongSerializer.INSTANCE.serialize(r.getClusterPosition(), stream, startPosition + OShortSerializer.SHORT_SIZE); + } + + public ORecordId deserializeNativeObject(byte[] stream, int startPosition) { + final int clusterId = OShortSerializer.INSTANCE.deserializeNative(stream, startPosition); + // Wrong implementation but needed for binary compatibility should be used deserializeNative + final long clusterPosition = OLongSerializer.INSTANCE.deserialize(stream, startPosition + OShortSerializer.SHORT_SIZE); + return new ORecordId(clusterId, clusterPosition); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return RID_SIZE; + } + + @Override + public OIdentifiable preprocess(OIdentifiable value, Object... hints) { + if (value == null) + return null; + else + return value.getIdentity(); + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(OIdentifiable object, ByteBuffer buffer, Object... hints) { + final ORID r = object.getIdentity(); + + buffer.putShort((short) r.getClusterId()); + // Wrong implementation but needed for binary compatibility + byte[] stream = new byte[OLongSerializer.LONG_SIZE]; + OLongSerializer.INSTANCE.serialize(r.getClusterPosition(), stream, 0); + buffer.put(stream); + } + + /** + * {@inheritDoc} + */ + @Override + public OIdentifiable deserializeFromByteBufferObject(ByteBuffer buffer) { + final int clusterId = buffer.getShort(); + + final byte[] stream = new byte[OLongSerializer.LONG_SIZE]; + buffer.get(stream); + // Wrong implementation but needed for binary compatibility + final long clusterPosition = OLongSerializer.INSTANCE.deserialize(stream, 0); + + return new ORecordId(clusterId, clusterPosition); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return RID_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public OIdentifiable deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final int clusterId = walChanges.getShortValue(buffer, offset); + + // Wrong implementation but needed for binary compatibility + final long clusterPosition = OLongSerializer.INSTANCE + .deserialize(walChanges.getBinaryValue(buffer, offset + OShortSerializer.SHORT_SIZE, OLongSerializer.LONG_SIZE), 0); + + // final long clusterPosition = OLongSerializer.INSTANCE + // .deserializeFromDirectMemory(pointer, offset + OShortSerializer.SHORT_SIZE); + + return new ORecordId(clusterId, clusterPosition); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return RID_SIZE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OCompositeKeySerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OCompositeKeySerializer.java new file mode 100644 index 00000000000..5edd61cb66d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OCompositeKeySerializer.java @@ -0,0 +1,396 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.binary.impl.index; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.ONullSerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.index.OCompositeKey; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.OMemoryInputStream; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerStringAbstract; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializer; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Serializer that is used for serialization of {@link OCompositeKey} keys in index. + * + * @author Andrey Lomakin + * @since 29.07.11 + */ +public class OCompositeKeySerializer implements OBinarySerializer, OStreamSerializer { + + public static final String NAME = "cks"; + + public static final OCompositeKeySerializer INSTANCE = new OCompositeKeySerializer(); + public static final byte ID = 14; + + public int getObjectSize(OCompositeKey compositeKey, Object... hints) { + final OType[] types = getKeyTypes(hints); + + final List keys = compositeKey.getKeys(); + + int size = 2 * OIntegerSerializer.INT_SIZE; + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keys.size(); i++) { + final Object key = keys.get(i); + + if (key != null) { + final OType type; + if (types.length > i) + type = types[i]; + else + type = OType.getTypeByClass(key.getClass()); + + size += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + ((OBinarySerializer) factory.getObjectSerializer(type)) + .getObjectSize(key); + } else { + size += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + ONullSerializer.INSTANCE.getObjectSize(null); + } + } + + return size; + } + + public void serialize(OCompositeKey compositeKey, byte[] stream, int startPosition, Object... hints) { + final OType[] types = getKeyTypes(hints); + + final List keys = compositeKey.getKeys(); + final int keysSize = keys.size(); + + final int oldStartPosition = startPosition; + + startPosition += OIntegerSerializer.INT_SIZE; + + OIntegerSerializer.INSTANCE.serializeLiteral(keysSize, stream, startPosition); + + startPosition += OIntegerSerializer.INT_SIZE; + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + + for (int i = 0; i < keys.size(); i++) { + final Object key = keys.get(i); + + OBinarySerializer binarySerializer; + if (key != null) { + final OType type; + if (types.length > i) + type = types[i]; + else + type = OType.getTypeByClass(key.getClass()); + + binarySerializer = factory.getObjectSerializer(type); + } else + binarySerializer = ONullSerializer.INSTANCE; + + stream[startPosition] = binarySerializer.getId(); + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + binarySerializer.serialize(key, stream, startPosition); + startPosition += binarySerializer.getObjectSize(key); + } + + OIntegerSerializer.INSTANCE.serializeLiteral((startPosition - oldStartPosition), stream, oldStartPosition); + } + + @SuppressWarnings("unchecked") + public OCompositeKey deserialize(byte[] stream, int startPosition) { + final OCompositeKey compositeKey = new OCompositeKey(); + + startPosition += OIntegerSerializer.INT_SIZE; + + final int keysSize = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + startPosition += OIntegerSerializer.INSTANCE.getObjectSize(keysSize); + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keysSize; i++) { + final byte serializerId = stream[startPosition]; + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + OBinarySerializer binarySerializer = (OBinarySerializer) factory.getObjectSerializer(serializerId); + final Object key = binarySerializer.deserialize(stream, startPosition); + compositeKey.addKey(key); + + startPosition += binarySerializer.getObjectSize(key); + } + + return compositeKey; + } + + public int getObjectSize(byte[] stream, int startPosition) { + return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition); + } + + public byte getId() { + return ID; + } + + public byte[] toStream(final Object iObject) throws IOException { + throw new UnsupportedOperationException("CSV storage format is out of dated and is not supported."); + } + + public Object fromStream(final byte[] iStream) throws IOException { + final OCompositeKey compositeKey = new OCompositeKey(); + final OMemoryInputStream inputStream = new OMemoryInputStream(iStream); + + final int keysSize = inputStream.getAsInteger(); + for (int i = 0; i < keysSize; i++) { + final byte[] keyBytes = inputStream.getAsByteArray(); + final String keyString = new String(keyBytes,"UTF-8"); + final int typeSeparatorPos = keyString.indexOf(','); + final OType type = OType.valueOf(keyString.substring(0, typeSeparatorPos)); + compositeKey.addKey(ORecordSerializerStringAbstract.simpleValueFromStream(keyString.substring(typeSeparatorPos + 1), type)); + } + return compositeKey; + } + + public String getName() { + return NAME; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition); + } + + public void serializeNativeObject(OCompositeKey compositeKey, byte[] stream, int startPosition, Object... hints) { + final OType[] types = getKeyTypes(hints); + + final List keys = compositeKey.getKeys(); + final int keysSize = keys.size(); + + final int oldStartPosition = startPosition; + + startPosition += OIntegerSerializer.INT_SIZE; + + OIntegerSerializer.INSTANCE.serializeNative(keysSize, stream, startPosition); + + startPosition += OIntegerSerializer.INT_SIZE; + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + + for (int i = 0; i < keys.size(); i++) { + final Object key = keys.get(i); + OBinarySerializer binarySerializer; + if (key != null) { + final OType type; + if (types.length > i) + type = types[i]; + else + type = OType.getTypeByClass(key.getClass()); + + binarySerializer = factory.getObjectSerializer(type); + } else + binarySerializer = ONullSerializer.INSTANCE; + + stream[startPosition] = binarySerializer.getId(); + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + binarySerializer.serializeNativeObject(key, stream, startPosition); + startPosition += binarySerializer.getObjectSize(key); + } + + OIntegerSerializer.INSTANCE.serializeNative((startPosition - oldStartPosition), stream, oldStartPosition); + } + + public OCompositeKey deserializeNativeObject(byte[] stream, int startPosition) { + final OCompositeKey compositeKey = new OCompositeKey(); + + startPosition += OIntegerSerializer.INT_SIZE; + + final int keysSize = OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition); + startPosition += OIntegerSerializer.INSTANCE.getObjectSize(keysSize); + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keysSize; i++) { + final byte serializerId = stream[startPosition]; + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + OBinarySerializer binarySerializer = (OBinarySerializer) factory.getObjectSerializer(serializerId); + final Object key = binarySerializer.deserializeNativeObject(stream, startPosition); + compositeKey.addKey(key); + + startPosition += binarySerializer.getObjectSize(key); + } + + return compositeKey; + } + + private OType[] getKeyTypes(Object[] hints) { + final OType[] types; + + if (hints != null && hints.length > 0) + types = (OType[]) hints; + else + types = OCommonConst.EMPTY_TYPES_ARRAY; + return types; + } + + public boolean isFixedLength() { + return false; + } + + public int getFixedLength() { + return 0; + } + + @Override + public OCompositeKey preprocess(OCompositeKey value, Object... hints) { + if (value == null) + return null; + + final OType[] types = getKeyTypes(hints); + + final List keys = value.getKeys(); + final OCompositeKey compositeKey = new OCompositeKey(); + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keys.size(); i++) { + final Object key = keys.get(i); + + final OType type; + if (types.length > i) + type = types[i]; + else + type = OType.getTypeByClass(key.getClass()); + + OBinarySerializer keySerializer = ((OBinarySerializer) factory.getObjectSerializer(type)); + compositeKey.addKey(keySerializer.preprocess(key)); + } + + return compositeKey; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(OCompositeKey object, ByteBuffer buffer, Object... hints) { + final OType[] types = getKeyTypes(hints); + + final List keys = object.getKeys(); + final int keysSize = keys.size(); + + final int oldStartOffset = buffer.position(); + buffer.position(oldStartOffset + OIntegerSerializer.INT_SIZE); + + buffer.putInt(keysSize); + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + + for (int i = 0; i < keys.size(); i++) { + final Object key = keys.get(i); + + OBinarySerializer binarySerializer; + if (key != null) { + final OType type; + if (types.length > i) + type = types[i]; + else + type = OType.getTypeByClass(key.getClass()); + + binarySerializer = factory.getObjectSerializer(type); + } else + binarySerializer = ONullSerializer.INSTANCE; + + buffer.put(binarySerializer.getId()); + binarySerializer.serializeInByteBufferObject(key, buffer); + } + + final int finalPosition = buffer.position(); + final int serializedSize = buffer.position() - oldStartOffset; + + buffer.position(oldStartOffset); + buffer.putInt(serializedSize); + + buffer.position(finalPosition); + } + + /** + * {@inheritDoc} + */ + @Override + public OCompositeKey deserializeFromByteBufferObject(ByteBuffer buffer) { + final OCompositeKey compositeKey = new OCompositeKey(); + + buffer.position(buffer.position() + OIntegerSerializer.INT_SIZE); + final int keysSize = buffer.getInt(); + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keysSize; i++) { + final byte serializerId = buffer.get(); + OBinarySerializer binarySerializer = (OBinarySerializer) factory.getObjectSerializer(serializerId); + final Object key = binarySerializer.deserializeFromByteBufferObject(buffer); + compositeKey.addKey(key); + } + + return compositeKey; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return buffer.getInt(); + } + + /** + * {@inheritDoc} + */ + @Override + public OCompositeKey deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final OCompositeKey compositeKey = new OCompositeKey(); + + offset += OIntegerSerializer.INT_SIZE; + + final int keysSize = walChanges.getIntValue(buffer, offset); + offset += OIntegerSerializer.INT_SIZE; + + final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance(); + for (int i = 0; i < keysSize; i++) { + final byte serializerId = walChanges.getByteValue(buffer, offset); + offset += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + OBinarySerializer binarySerializer = (OBinarySerializer) factory.getObjectSerializer(serializerId); + final Object key = binarySerializer.deserializeFromByteBufferObject(buffer, walChanges, offset); + compositeKey.addKey(key); + + offset += binarySerializer.getObjectSize(key); + } + + return compositeKey; + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return walChanges.getIntValue(buffer, offset); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OSimpleKeySerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OSimpleKeySerializer.java new file mode 100644 index 00000000000..494705a43eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/binary/impl/index/OSimpleKeySerializer.java @@ -0,0 +1,196 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.binary.impl.index; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.nio.ByteBuffer; + +/** + * Serializer that is used for serialization of non {@link com.orientechnologies.orient.core.index.OCompositeKey} keys in index. + * + * @author Andrey Lomakin + * @since 31.03.12 + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class OSimpleKeySerializer> implements OBinarySerializer { + + private OType type; + private OBinarySerializer binarySerializer; + + public static final byte ID = 15; + public static final String NAME = "bsks"; + + public OSimpleKeySerializer() { + } + + public OSimpleKeySerializer(final OType iType) { + type = iType; + + binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(iType); + } + + public int getObjectSize(T key, Object... hints) { + init(key, hints); + return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer.getObjectSize(key); + } + + public void serialize(T key, byte[] stream, int startPosition, Object... hints) { + init(key, hints); + stream[startPosition] = binarySerializer.getId(); + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + binarySerializer.serialize(key, stream, startPosition); + } + + public T deserialize(byte[] stream, int startPosition) { + final byte typeId = stream[startPosition]; + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + init(typeId); + return (T) binarySerializer.deserialize(stream, startPosition); + } + + public int getObjectSize(byte[] stream, int startPosition) { + final byte serializerId = stream[startPosition]; + init(serializerId); + return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer + .getObjectSize(stream, startPosition + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE); + } + + public byte getId() { + return ID; + } + + protected void init(T key, Object[] hints) { + if (binarySerializer == null) { + final OType[] types; + + if (hints != null && hints.length > 0) + types = (OType[]) hints; + else + types = OCommonConst.EMPTY_TYPES_ARRAY; + + if (types.length > 0) + type = types[0]; + else + type = OType.getTypeByClass(key.getClass()); + + binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(type); + } + } + + protected void init(byte serializerId) { + if (binarySerializer == null) + binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(serializerId); + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + final byte serializerId = stream[startPosition]; + init(serializerId); + return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer + .getObjectSizeNative(stream, startPosition + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE); + } + + public void serializeNativeObject(T key, byte[] stream, int startPosition, Object... hints) { + init(key, hints); + stream[startPosition] = binarySerializer.getId(); + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + binarySerializer.serializeNativeObject(key, stream, startPosition); + } + + public T deserializeNativeObject(byte[] stream, int startPosition) { + final byte typeId = stream[startPosition]; + startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + + init(typeId); + return (T) binarySerializer.deserializeNativeObject(stream, startPosition); + } + + public boolean isFixedLength() { + return binarySerializer.isFixedLength(); + } + + public int getFixedLength() { + return binarySerializer.getFixedLength() + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE; + } + + @Override + public T preprocess(T value, Object... hints) { + init(value, hints); + + return (T) binarySerializer.preprocess(value); + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(T object, ByteBuffer buffer, Object... hints) { + init(object, hints); + buffer.put(binarySerializer.getId()); + binarySerializer.serializeInByteBufferObject(object, buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public T deserializeFromByteBufferObject(ByteBuffer buffer) { + final byte typeId = buffer.get(); + + init(typeId); + return (T) binarySerializer.deserializeFromByteBufferObject(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + final byte serializerId = buffer.get(); + init(serializerId); + return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer.getObjectSizeInByteBuffer(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public T deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final byte typeId = walChanges.getByteValue(buffer, offset++); + + init(typeId); + return (T) binarySerializer.deserializeFromByteBufferObject(buffer, walChanges, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer + .getObjectSizeInByteBuffer(buffer, walChanges, OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + offset); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializer.java new file mode 100644 index 00000000000..ea3d64d5903 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializer.java @@ -0,0 +1,26 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.object; + +public interface OObjectSerializer { + public Object serializeFieldValue(Class iClass, LOCAL_TYPE iFieldValue); + + public Object unserializeFieldValue(Class iClass, DB_TYPE iFieldValue); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperDocument.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperDocument.java new file mode 100644 index 00000000000..f956b67efed --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperDocument.java @@ -0,0 +1,98 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.serialization.serializer.object; + +import com.orientechnologies.orient.core.annotation.ODocumentInstance; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.entity.OEntityManager; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * @author luca.molino + * + */ +public class OObjectSerializerHelperDocument implements OObjectSerializerHelperInterface { + + private final Set classes = new HashSet(); + private HashMap, Field> boundDocumentFields = new HashMap, Field>(); + + public ODocument toStream(Object iPojo, ODocument iRecord, OEntityManager iEntityManager, OClass schemaClass, OUserObject2RecordHandler iObj2RecHandler, ODatabaseObject db, boolean iSaveOnlyDirty) { + return null; + } + + public String getDocumentBoundField(Class iClass) { + getClassFields(iClass); + final Field f = boundDocumentFields.get(iClass); + return f != null ? f.getName() : null; + } + + public Object getFieldValue(Object iPojo, String iProperty) { + return null; + } + + public void invokeCallback(Object iPojo, ODocument iDocument, Class iAnnotation) { + } + + private void getClassFields(final Class iClass) { + if (iClass.getName().startsWith("java.lang")) + return; + + synchronized (classes) { + if (classes.contains(iClass.getName())) + return; + + analyzeClass(iClass); + } + } + + protected void analyzeClass(final Class iClass) { + classes.add(iClass.getName()); + + int fieldModifier; + + for (Class currentClass = iClass; currentClass != Object.class; ) { + for (Field f : currentClass.getDeclaredFields()) { + fieldModifier = f.getModifiers(); + if (Modifier.isStatic(fieldModifier) || Modifier.isNative(fieldModifier) || Modifier.isTransient(fieldModifier)) + continue; + + if (f.getName().equals("this$0")) + continue; + + // CHECK FOR AUTO-BINDING + if (f.getAnnotation(ODocumentInstance.class) != null) + // BOUND DOCUMENT ON IT + boundDocumentFields.put(iClass, f); + } + currentClass = currentClass.getSuperclass(); + + if (currentClass.equals(ODocument.class)) + // POJO EXTENDS ODOCUMENT: SPECIAL CASE: AVOID TO CONSIDER + // ODOCUMENT FIELDS + break; + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperInterface.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperInterface.java new file mode 100644 index 00000000000..61f0d3f5e15 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperInterface.java @@ -0,0 +1,41 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.serialization.serializer.object; + +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.entity.OEntityManager; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author luca.molino + * + */ +public interface OObjectSerializerHelperInterface { + + public ODocument toStream(final Object iPojo, final ODocument iRecord, final OEntityManager iEntityManager, + final OClass schemaClass, final OUserObject2RecordHandler iObj2RecHandler, final ODatabaseObject db, + final boolean iSaveOnlyDirty); + + public String getDocumentBoundField(final Class iClass); + + public Object getFieldValue(final Object iPojo, final String iProperty); + + public void invokeCallback(final Object iPojo, final ODocument iDocument, final Class iAnnotation); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperManager.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperManager.java new file mode 100644 index 00000000000..0de118ef6d6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/object/OObjectSerializerHelperManager.java @@ -0,0 +1,63 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.serialization.serializer.object; + +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.entity.OEntityManager; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Manager class that manages common operations against objects + * + * @author luca.molino + * + */ +public class OObjectSerializerHelperManager { + + private static final OObjectSerializerHelperManager instance = new OObjectSerializerHelperManager(); + + private OObjectSerializerHelperInterface serializerHelper = new OObjectSerializerHelperDocument(); + + public static OObjectSerializerHelperManager getInstance() { + return instance; + } + + public ODocument toStream(final Object iPojo, final ODocument iRecord, final OEntityManager iEntityManager, + final OClass schemaClass, final OUserObject2RecordHandler iObj2RecHandler, final ODatabaseObject db, + final boolean iSaveOnlyDirty) { + return serializerHelper.toStream(iPojo, iRecord, iEntityManager, schemaClass, iObj2RecHandler, db, iSaveOnlyDirty); + } + + public String getDocumentBoundField(final Class iClass) { + return serializerHelper.getDocumentBoundField(iClass); + } + + public Object getFieldValue(final Object iPojo, final String iProperty) { + return serializerHelper.getFieldValue(iPojo, iProperty); + } + + public void invokeCallback(final Object iPojo, final ODocument iDocument, final Class iAnnotation) { + serializerHelper.invokeCallback(iPojo, iDocument, iAnnotation); + } + + public void registerHelper(OObjectSerializerHelperInterface iSerializerHelper) { + serializerHelper = iSerializerHelper; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSaveThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSaveThreadLocal.java new file mode 100755 index 00000000000..2f80a7ff6f3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSaveThreadLocal.java @@ -0,0 +1,62 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.record.ORecord; + +/** + * Thread local to store last document to save. This is used by Auto Merge Conflict Strategy o get he pending record (not visible at + * storage level). + * + * @author Luca Garulli + */ +public class ORecordSaveThreadLocal extends ThreadLocal { + public static ORecordSaveThreadLocal INSTANCE = new ORecordSaveThreadLocal(); + + static { + Orient.instance().registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new ORecordSaveThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } + + public static ORecord getLast() { + return INSTANCE.get(); + } + + public static void setLast(final ORecord document) { + INSTANCE.set(document); + } + + public static void removeLast() { + INSTANCE.set(null); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializer.java new file mode 100644 index 00000000000..0575187089b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializer.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record; + +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +public interface ORecordSerializer { + ORecord fromStream(byte[] iSource, ORecord iRecord, String[] iFields); + + byte[] toStream(ORecord iSource, boolean iOnlyDelta); + + byte[] writeClassOnly(ORecord iSource); + + int getCurrentVersion(); + + int getMinSupportedVersion(); + + String[] getFieldNames(ODocument reference, byte[] iSource); + + boolean getSupportBinaryEvaluate(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerFactory.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerFactory.java new file mode 100644 index 00000000000..61a7ece6666 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerFactory.java @@ -0,0 +1,107 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerNetwork; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerJSON; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV; + +/** + * Factory of record serialized. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class ORecordSerializerFactory { + private static final ORecordSerializerFactory instance = new ORecordSerializerFactory(); + + private Map implementations = new HashMap(); + + @Deprecated + private ORecordSerializer defaultRecordFormat; + + public ORecordSerializerFactory() { + defaultRecordFormat = new ORecordSerializerRaw(); + + register(ORecordSerializerSchemaAware2CSV.NAME, ORecordSerializerSchemaAware2CSV.INSTANCE); + register(ORecordSerializerJSON.NAME, ORecordSerializerJSON.INSTANCE); + register(ORecordSerializerRaw.NAME, defaultRecordFormat); + register(ORecordSerializerBinary.NAME, ORecordSerializerBinary.INSTANCE); + register(ORecordSerializerNetwork.NAME, ORecordSerializerNetwork.INSTANCE); + } + + /** + * Registers record serializer implementation. + * + * @param iName + * Name to register, use JSON to overwrite default JSON serializer + * @param iInstance + * Serializer implementation + */ + public void register(final String iName, final ORecordSerializer iInstance) { + implementations.put(iName, iInstance); + } + + public Collection getFormats() { + return implementations.values(); + } + + public ORecordSerializer getFormat(final String iFormatName) { + if (iFormatName == null) + return null; + + return implementations.get(iFormatName); + } + + // Never used so can be deprecate. + @Deprecated + public ORecordSerializer getFormatForObject(final Object iObject, final String iFormatName) { + if (iObject == null) + return null; + + ORecordSerializer recordFormat = null; + if (iFormatName != null) + recordFormat = implementations.get(iObject.getClass().getSimpleName() + "2" + iFormatName); + + if (recordFormat == null) + recordFormat = defaultRecordFormat; + + return recordFormat; + } + + @Deprecated + public ORecordSerializer getDefaultRecordFormat() { + return defaultRecordFormat; + } + + @Deprecated + public void setDefaultRecordFormat(final ORecordSerializer iDefaultFormat) { + this.defaultRecordFormat = iDefaultFormat; + } + + public static ORecordSerializerFactory instance() { + return instance; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerRaw.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerRaw.java new file mode 100755 index 00000000000..5b0ad18e1b1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/ORecordSerializerRaw.java @@ -0,0 +1,83 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.OBlob; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ORecordBytes; + +public class ORecordSerializerRaw implements ORecordSerializer { + public static final String NAME = "ORecordDocumentRaw"; + + public ORecord fromStream(final byte[] iSource) { + return new ORecordBytes(iSource); + } + + @Override + public int getCurrentVersion() { + return 0; + } + + @Override + public int getMinSupportedVersion() { + return 0; + } + + @Override + public String[] getFieldNames(ODocument reference, byte[] iSource) { + return null; + } + + @Override + public String toString() { + return NAME; + } + + public ORecord fromStream(final byte[] iSource, final ORecord iRecord, String[] iFields) { + final OBlob record = (OBlob) iRecord; + record.reset(); + record.fromStream(iSource); + + return record; + } + + @Override + public byte[] writeClassOnly(ORecord iSource) { + return new byte[] {}; + } + + public byte[] toStream(final ORecord iSource, boolean iOnlyDelta) { + try { + return iSource.toStream(); + } catch (Exception e) { + final String message = "Error on unmarshalling object in binary format: " + iSource.getIdentity(); + OLogManager.instance().error(this, message, e); + throw OException.wrapException(new OSerializationException(message), e); + } + } + + public boolean getSupportBinaryEvaluate() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/OSerializationThreadLocal.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/OSerializationThreadLocal.java new file mode 100644 index 00000000000..5b8568f701f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/OSerializationThreadLocal.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record; + +import java.util.HashSet; +import java.util.Set; + +import com.orientechnologies.orient.core.OOrientListenerAbstract; +import com.orientechnologies.orient.core.OOrientShutdownListener; +import com.orientechnologies.orient.core.OOrientStartupListener; +import com.orientechnologies.orient.core.Orient; + +public class OSerializationThreadLocal extends ThreadLocal> { + public static volatile OSerializationThreadLocal INSTANCE = new OSerializationThreadLocal(); + + static { + Orient.instance().registerListener(new OOrientListenerAbstract() { + @Override + public void onStartup() { + if (INSTANCE == null) + INSTANCE = new OSerializationThreadLocal(); + } + + @Override + public void onShutdown() { + INSTANCE = null; + } + }); + } + + @Override + protected Set initialValue() { + return new HashSet(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/BytesContainer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/BytesContainer.java new file mode 100644 index 00000000000..29650873aac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/BytesContainer.java @@ -0,0 +1,72 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +public class BytesContainer { + + public byte[] bytes; + public int offset; + + public BytesContainer(byte[] iSource) { + bytes = iSource; + } + + public BytesContainer() { + bytes = new byte[64]; + } + + public BytesContainer(final byte[] iBytes, final int iOffset) { + this.bytes = iBytes; + this.offset = iOffset; + } + + public BytesContainer copy() { + return new BytesContainer(bytes, offset); + } + + public int alloc(final int toAlloc) { + final int cur = offset; + offset += toAlloc; + if (bytes.length < offset) + resize(); + return cur; + } + + public BytesContainer skip(final int read) { + offset += read; + return this; + } + + public byte[] fitBytes() { + final byte[] fitted = new byte[offset]; + System.arraycopy(bytes, 0, fitted, 0, offset); + return fitted; + } + + private void resize() { + int newLength = bytes.length; + while (newLength < offset) + newLength *= 2; + final byte[] newBytes = new byte[newLength]; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length); + bytes = newBytes; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparator.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparator.java new file mode 100644 index 00000000000..b4a550d36dc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparator.java @@ -0,0 +1,59 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Compares types at binary level: super fast, using of literals as much as it can. + * + * @author Luca Garulli + */ +public interface OBinaryComparator { + /** + * Compares if two binary values are the same. + * + * @param iFirstValue + * First value to compare + * @param iSecondValue + * Second value to compare + * @return true if they match, otherwise false + */ + boolean isEqual(OBinaryField iFirstValue, OBinaryField iSecondValue); + + /** + * Compares two binary values executing also conversion between types. + * + * @param iValue1 + * First value to compare + * @param iValue2 + * Second value to compare + * @return 0 if they matches, >0 if first value is major than second, <0 in case is minor + */ + int compare(OBinaryField iValue1, OBinaryField iValue2); + + /** + * Returns true if the type is binary comparable + * + * @return + */ + boolean isBinaryComparable(OType iType); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparatorV0.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparatorV0.java new file mode 100755 index 00000000000..0a4ff58b631 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryComparatorV0.java @@ -0,0 +1,1259 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.serialization.types.ODecimalSerializer; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.collate.ODefaultCollate; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Implementation v0 of comparator based on protocol v0. + * + * @author Luca Garulli + */ +public class OBinaryComparatorV0 implements OBinaryComparator { + + public OBinaryComparatorV0() { + } + + public boolean isBinaryComparable(final OType iType) { + switch (iType) { + case INTEGER: + case LONG: + case DATETIME: + case SHORT: + case STRING: + case DOUBLE: + case FLOAT: + case BYTE: + case BOOLEAN: + case DATE: + case BINARY: + case LINK: + case DECIMAL: + return true; + default: + return false; + } + } + + /** + * Compares if 2 field values are the same. + * + * @param iField1 + * First value to compare + * @param iField2 + * Second value to compare + * @return true if they match, otherwise false + */ + @Override + public boolean isEqual(final OBinaryField iField1, final OBinaryField iField2) { + final BytesContainer fieldValue1 = iField1.bytes; + final int offset1 = fieldValue1.offset; + + final BytesContainer fieldValue2 = iField2.bytes; + final int offset2 = fieldValue2.offset; + + try { + switch (iField1.type) { + case INTEGER: { + final int value1 = OVarIntSerializer.readAsInteger(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case DATE: { + final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY); + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + return Integer.parseInt(ORecordSerializerBinaryV0.readString(fieldValue2)) == value1; + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.intValue(); + } + } + break; + } + + case LONG: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case DATE: { + final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY); + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + return Long.parseLong(ORecordSerializerBinaryV0.readString(fieldValue2)) == value1; + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.longValue(); + } + } + break; + } + + case SHORT: { + final short value1 = OVarIntSerializer.readAsShort(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case DATE: { + final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY); + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + return Short.parseShort(ORecordSerializerBinaryV0.readString(fieldValue2)) == value1; + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.shortValue(); + } + } + break; + } + + case STRING: { + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return Integer.parseInt(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return Long.parseLong(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return Long.parseLong(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return Short.parseShort(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return Byte.parseByte(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return Float.parseFloat(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return Double.parseDouble(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + case STRING: { + final int len1 = OVarIntSerializer.readAsInteger(fieldValue1); + final int len2 = OVarIntSerializer.readAsInteger(fieldValue2); + + if (len1 != len2) + return false; + + final OCollate collate = (iField1.collate != null && !ODefaultCollate.NAME.equals(iField1.collate.getName())) ? iField1.collate + : (iField2.collate != null && !ODefaultCollate.NAME.equals(iField2.collate.getName()) ? iField2.collate : null); + + if (collate != null) { + final String str1 = (String) collate.transform(ORecordSerializerBinaryV0.stringFromBytes(fieldValue1.bytes, + fieldValue1.offset, len1)); + final String str2 = (String) collate.transform(ORecordSerializerBinaryV0.stringFromBytes(fieldValue2.bytes, + fieldValue2.offset, len2)); + + return str1.equals(str2); + + } else { + for (int i = 0; i < len1; ++i) { + if (fieldValue1.bytes[fieldValue1.offset + i] != fieldValue2.bytes[fieldValue2.offset + i]) + return false; + } + } + return true; + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return new BigDecimal(ORecordSerializerBinaryV0.readString(fieldValue1)).equals(value2); + } + case BOOLEAN: { + final boolean value2 = ORecordSerializerBinaryV0.readByte(fieldValue2) == 1; + return Boolean.parseBoolean(ORecordSerializerBinaryV0.readString(fieldValue1)) == value2; + } + } + break; + } + + case DOUBLE: { + final long value1AsLong = ORecordSerializerBinaryV0.readLong(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case SHORT: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2AsLong = ORecordSerializerBinaryV0.readLong(fieldValue2); + return value1AsLong == value2AsLong; + } + case STRING: { + final double value1 = Double.longBitsToDouble(value1AsLong); + return Double.parseDouble(ORecordSerializerBinaryV0.readString(fieldValue2)) == value1; + } + case DECIMAL: { + final double value1 = Double.longBitsToDouble(value1AsLong); + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.doubleValue(); + } + } + break; + } + + case FLOAT: { + final int value1AsInt = ORecordSerializerBinaryV0.readInteger(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case SHORT: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2AsInt = ORecordSerializerBinaryV0.readInteger(fieldValue2); + return value1AsInt == value2AsInt; + } + case DOUBLE: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + final float value1 = Float.intBitsToFloat(value1AsInt); + return Float.parseFloat(ORecordSerializerBinaryV0.readString(fieldValue2)) == value1; + } + case DECIMAL: { + final float value1 = Float.intBitsToFloat(value1AsInt); + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.floatValue(); + } + } + break; + } + + case BYTE: { + final byte value1 = ORecordSerializerBinaryV0.readByte(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + final byte value2 = Byte.parseByte((ORecordSerializerBinaryV0.readString(fieldValue2))); + return value1 == value2; + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.byteValue(); + } + } + break; + } + + case BOOLEAN: { + final boolean value1 = ORecordSerializerBinaryV0.readByte(fieldValue1) == 1; + + switch (iField2.type) { + case BOOLEAN: { + final boolean value2 = ORecordSerializerBinaryV0.readByte(fieldValue2) == 1; + return value1 == value2; + } + case STRING: { + final String str = ORecordSerializerBinaryV0.readString(fieldValue2); + return Boolean.parseBoolean(str) == value1; + } + } + break; + } + + case DATE: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + long value2 = OVarIntSerializer.readAsLong(fieldValue2); + value2 = ORecordSerializerBinaryV0.convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2); + return value1 == value2; + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + final String value2AsString = ORecordSerializerBinaryV0.readString(fieldValue2); + + if (OIOUtils.isLong(value2AsString)) { + final long value2 = Long.parseLong(value2AsString); + return value1 == value2; + } + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + long value2 = value2AsDate.getTime(); + value2 = ORecordSerializerBinaryV0 + .convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2); + return value1 == value2; + } catch (ParseException e) { + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + final long value2 = value2AsDate.getTime(); + return value1 == value2; + } catch (ParseException e1) { + return new Date(value1).toString().equals(value2AsString); + } + } + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.longValue(); + } + } + break; + } + + case DATETIME: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1 == value2; + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1 == value2; + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return value1 == value2; + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1 == value2; + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1 == value2; + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1 == value2; + } + case STRING: { + final String value2AsString = ORecordSerializerBinaryV0.readString(fieldValue2); + + if (OIOUtils.isLong(value2AsString)) { + final long value2 = Long.parseLong(value2AsString); + return value1 == value2; + } + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateTimeFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + final long value2 = value2AsDate.getTime(); + return value1 == value2; + } catch (ParseException e) { + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + final long value2 = value2AsDate.getTime(); + return value1 == value2; + } catch (ParseException e1) { + return new Date(value1).toString().equals(value2AsString); + } + } + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1 == value2.longValue(); + } + } + break; + } + + case BINARY: { + switch (iField2.type) { + case BINARY: { + final int length1 = OVarIntSerializer.readAsInteger(fieldValue1); + final int length2 = OVarIntSerializer.readAsInteger(fieldValue2); + if (length1 != length2) + return false; + + for (int i = 0; i < length1; ++i) { + if (fieldValue1.bytes[fieldValue1.offset + i] != fieldValue2.bytes[fieldValue2.offset + i]) + return false; + } + return true; + } + } + break; + } + + case LINK: { + switch (iField2.type) { + case LINK: { + final int clusterId1 = OVarIntSerializer.readAsInteger(fieldValue1); + final int clusterId2 = OVarIntSerializer.readAsInteger(fieldValue2); + if (clusterId1 != clusterId2) + return false; + + final long clusterPos1 = OVarIntSerializer.readAsLong(fieldValue1); + final long clusterPos2 = OVarIntSerializer.readAsLong(fieldValue2); + if (clusterPos1 == clusterPos2) + return true; + break; + } + case STRING: { + return ORecordSerializerBinaryV0.readOptimizedLink(fieldValue1).toString() + .equals(ORecordSerializerBinaryV0.readString(fieldValue2)); + } + } + break; + } + + case DECIMAL: { + BigDecimal value1 = ODecimalSerializer.INSTANCE.deserialize(fieldValue1.bytes, fieldValue1.offset); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1.equals(new BigDecimal(value2).setScale(value1.scale())); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1.equals(new BigDecimal(value2)); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1.equals(new BigDecimal(value2)); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1.equals(new BigDecimal(value2)); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1.equals(new BigDecimal(value2)); + } + case STRING: { + return value1.toString().equals(ORecordSerializerBinaryV0.readString(fieldValue2)); + } + case DECIMAL: { + BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + int maxScale = Math.max(value1.scale(), value2.scale()); + value1 = value1.setScale(maxScale, BigDecimal.ROUND_DOWN); + value2 = value2.setScale(maxScale, BigDecimal.ROUND_DOWN); + return value1.equals(value2); + } + } + break; + } + } + } finally { + fieldValue1.offset = offset1; + fieldValue2.offset = offset2; + } + + return false; + } + + /** + * Compares two values executing also conversion between types. + * + * @param iField1 + * First value to compare + * @param iField2 + * Second value to compare + * @return 0 if they matches, >0 if first value is major than second, <0 in case is minor + */ + @Override + public int compare(final OBinaryField iField1, final OBinaryField iField2) { + final BytesContainer fieldValue1 = iField1.bytes; + final int offset1 = fieldValue1.offset; + + final BytesContainer fieldValue2 = iField2.bytes; + final int offset2 = fieldValue2.offset; + + try { + switch (iField1.type) { + case INTEGER: { + final int value1 = OVarIntSerializer.readAsInteger(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Integer.toString(value1).compareTo(value2); + } + case DECIMAL: { + final int value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).intValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case LONG: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Long.toString(value1).compareTo(value2); + } + case DECIMAL: { + final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case SHORT: { + final short value1 = OVarIntSerializer.readAsShort(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Short.toString(value1).compareTo(value2); + } + case DECIMAL: { + final short value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).shortValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case STRING: { + final String value1 = ORecordSerializerBinaryV0.readString(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1.compareTo(Integer.toString(value2)); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1.compareTo(Long.toString(value2)); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return value1.compareTo(Long.toString(value2)); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1.compareTo(Short.toString(value2)); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1.compareTo(Byte.toString(value2)); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1.compareTo(Float.toString(value2)); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1.compareTo(Double.toString(value2)); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + + final OCollate collate = (iField1.collate != null && !ODefaultCollate.NAME.equals(iField1.collate.getName())) ? iField1.collate + : (iField2.collate != null && !ODefaultCollate.NAME.equals(iField2.collate.getName()) ? iField2.collate : null); + + if (collate != null) { + final String str1 = (String) collate.transform(value1); + final String str2 = (String) collate.transform(value2); + return str1.compareTo(str2); + } + + return value1.compareTo(value2); + } + case BOOLEAN: { + final boolean value2 = ORecordSerializerBinaryV0.readByte(fieldValue2) == 1; + return value1.compareTo(Boolean.toString(value2)); + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return new BigDecimal(value1).compareTo(value2); + } + } + break; + } + + case DOUBLE: { + final double value1 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue1)); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Double.toString(value1).compareTo(value2); + } + case DECIMAL: { + final double value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).doubleValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case FLOAT: { + final float value1 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue1)); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Float.toString(value1).compareTo(value2); + } + case DECIMAL: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Float.toString(value1).compareTo(value2); + } + } + break; + } + + case BYTE: { + final byte value1 = ORecordSerializerBinaryV0.readByte(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return Byte.toString(value1).compareTo(value2); + } + case DECIMAL: { + final byte value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).byteValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case BOOLEAN: { + final boolean value1 = ORecordSerializerBinaryV0.readByte(fieldValue1) == 1; + + switch (iField2.type) { + case BOOLEAN: { + final boolean value2 = ORecordSerializerBinaryV0.readByte(fieldValue2) == 1; + return (value1 == value2) ? 0 : value1 ? 1 : -1; + } + case STRING: { + final boolean value2 = Boolean.parseBoolean(ORecordSerializerBinaryV0.readString(fieldValue2)); + return (value1 == value2) ? 0 : value1 ? 1 : -1; + } + } + break; + } + + case DATETIME: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2AsString = ORecordSerializerBinaryV0.readString(fieldValue2); + + if (OIOUtils.isLong(value2AsString)) { + final long value2 = Long.parseLong(value2AsString); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateTimeFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + final long value2 = value2AsDate.getTime(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } catch (ParseException e) { + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + final long value2 = value2AsDate.getTime(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } catch (ParseException e1) { + return new Date(value1).toString().compareTo(value2AsString); + } + } + } + case DECIMAL: { + final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case DATE: { + final long value1 = OVarIntSerializer.readAsLong(fieldValue1) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DATE: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * ORecordSerializerBinaryV0.MILLISEC_PER_DAY; + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + case STRING: { + final String value2AsString = ORecordSerializerBinaryV0.readString(fieldValue2); + + if (OIOUtils.isLong(value2AsString)) { + final long value2 = Long.parseLong(value2AsString); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT); + final Date value2AsDate = dateFormat.parse(value2AsString); + long value2 = value2AsDate.getTime(); + value2 = ORecordSerializerBinaryV0.convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } catch (ParseException e) { + try { + final SimpleDateFormat dateFormat = db != null ? db.getStorage().getConfiguration().getDateFormatInstance() + : new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT); + + final Date value2AsDate = dateFormat.parse(value2AsString); + long value2 = value2AsDate.getTime(); + value2 = ORecordSerializerBinaryV0.convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } catch (ParseException e1) { + return new Date(value1).toString().compareTo(value2AsString); + } + } + } + case DECIMAL: { + final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue(); + return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1); + } + } + break; + } + + case BINARY: { + switch (iField2.type) { + case BINARY: { + final int length1 = OVarIntSerializer.readAsInteger(fieldValue1); + final int length2 = OVarIntSerializer.readAsInteger(fieldValue2); + + final int max = Math.min(length1, length2); + for (int i = 0; i < max; ++i) { + final byte b1 = fieldValue1.bytes[fieldValue1.offset + i]; + final byte b2 = fieldValue2.bytes[fieldValue2.offset + i]; + + if (b1 > b2) + return 1; + else if (b2 > b1) + return -1; + } + + if (length1 > length2) + return 1; + else if (length2 > length1) + return -1; + + // EQUALS + return 0; + } + } + break; + } + + case LINK: { + switch (iField2.type) { + case LINK: { + final int clusterId1 = OVarIntSerializer.readAsInteger(fieldValue1); + final int clusterId2 = OVarIntSerializer.readAsInteger(fieldValue2); + if (clusterId1 > clusterId2) + return 1; + else if (clusterId1 < clusterId2) + return -1; + else { + final long clusterPos1 = OVarIntSerializer.readAsLong(fieldValue1); + final long clusterPos2 = OVarIntSerializer.readAsLong(fieldValue2); + if (clusterPos1 > clusterPos2) + return 1; + else if (clusterPos1 < clusterPos2) + return -1; + return 0; + } + } + + case STRING: { + return ORecordSerializerBinaryV0.readOptimizedLink(fieldValue1).compareTo( + new ORecordId(ORecordSerializerBinaryV0.readString(fieldValue2))); + } + } + break; + } + + case DECIMAL: { + final BigDecimal value1 = ODecimalSerializer.INSTANCE.deserialize(fieldValue1.bytes, fieldValue1.offset); + + switch (iField2.type) { + case INTEGER: { + final int value2 = OVarIntSerializer.readAsInteger(fieldValue2); + return value1.compareTo(new BigDecimal(value2)); + } + case LONG: + case DATETIME: { + final long value2 = OVarIntSerializer.readAsLong(fieldValue2); + return value1.compareTo(new BigDecimal(value2)); + } + case SHORT: { + final short value2 = OVarIntSerializer.readAsShort(fieldValue2); + return value1.compareTo(new BigDecimal(value2)); + } + case FLOAT: { + final float value2 = Float.intBitsToFloat(ORecordSerializerBinaryV0.readInteger(fieldValue2)); + return value1.compareTo(new BigDecimal(value2)); + } + case DOUBLE: { + final double value2 = Double.longBitsToDouble(ORecordSerializerBinaryV0.readLong(fieldValue2)); + return value1.compareTo(new BigDecimal(value2)); + } + case STRING: { + final String value2 = ORecordSerializerBinaryV0.readString(fieldValue2); + return value1.toString().compareTo(value2); + } + case DECIMAL: { + final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset); + return value1.compareTo(value2); + } + case BYTE: { + final byte value2 = ORecordSerializerBinaryV0.readByte(fieldValue2); + return value1.compareTo(new BigDecimal(value2)); + } + } + break; + } + } + } finally { + fieldValue1.offset = offset1; + fieldValue2.offset = offset2; + } + + // NO COMPARE SUPPORTED, RETURN NON EQUALS + return 1; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryField.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryField.java new file mode 100644 index 00000000000..79c28b6e22b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OBinaryField.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Represents a binary field. + * + * @author Luca Garulli + */ +public class OBinaryField { + + public final String name; + public final OType type; + public final BytesContainer bytes; + public final OCollate collate; + + public OBinaryField(final String iName, final OType iType, final BytesContainer iBytes, final OCollate iCollate) { + name = iName; + type = iType; + bytes = iBytes; + collate = iCollate; + } + + public OBinaryField copy() { + return new OBinaryField(name, type, bytes.copy(), collate); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ODocumentSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ODocumentSerializer.java new file mode 100644 index 00000000000..cc0f50e93c6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ODocumentSerializer.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +public interface ODocumentSerializer { + + void serialize(ODocument document, BytesContainer bytes, boolean iClassOnly); + + int serializeValue(BytesContainer bytes, Object value, OType type, OType linkedType); + + void deserialize(ODocument document, BytesContainer bytes); + + void deserializePartial(ODocument document, BytesContainer bytes, String[] iFields); + + Object deserializeValue(BytesContainer bytes, OType type, ODocument ownerDocument); + + OBinaryField deserializeField(BytesContainer bytes, OClass iClass, String iFieldName); + + OBinaryComparator getComparator(); + + /** + * Returns the array of field names with no values. + * @param reference TODO + */ + String[] getFieldNames(ODocument reference, BytesContainer iBytes); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebug.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebug.java new file mode 100644 index 00000000000..a90e217af70 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebug.java @@ -0,0 +1,13 @@ +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import java.util.ArrayList; + +public class ORecordSerializationDebug { + + public String className; + public ArrayList properties; + public boolean readingFailure; + public RuntimeException readingException; + public int failPosition; + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebugProperty.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebugProperty.java new file mode 100644 index 00000000000..488cd075805 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializationDebugProperty.java @@ -0,0 +1,16 @@ +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +public class ORecordSerializationDebugProperty { + + public String name; + public int globalId; + public OType type; + public RuntimeException readingException; + public boolean faildToRead; + public int failPosition; + public Object value; + public int valuePos; + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinary.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinary.java new file mode 100644 index 00000000000..ca8f47ecee5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinary.java @@ -0,0 +1,144 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; + +public class ORecordSerializerBinary implements ORecordSerializer { + + public static final String NAME = "ORecordSerializerBinary"; + public static final ORecordSerializerBinary INSTANCE = new ORecordSerializerBinary(); + private static final byte CURRENT_RECORD_VERSION = 0; + + private ODocumentSerializer[] serializerByVersion; + + public ORecordSerializerBinary() { + serializerByVersion = new ODocumentSerializer[1]; + serializerByVersion[0] = new ORecordSerializerBinaryV0(); + } + + @Override + public int getCurrentVersion() { + return CURRENT_RECORD_VERSION; + } + + @Override + public int getMinSupportedVersion() { + return CURRENT_RECORD_VERSION; + } + + public ODocumentSerializer getSerializer(final int iVersion) { + return serializerByVersion[iVersion]; + } + + public ODocumentSerializer getCurrentSerializer() { + return serializerByVersion[serializerByVersion.length - 1]; + } + + @Override + public String toString() { + return NAME; + } + + @Override + public ORecord fromStream(final byte[] iSource, ORecord iRecord, final String[] iFields) { + if (iSource == null || iSource.length == 0) + return iRecord; + if (iRecord == null) + iRecord = new ODocument(); + else + checkTypeODocument(iRecord); + + final BytesContainer container = new BytesContainer(iSource).skip(1); + + try { + if (iFields != null && iFields.length > 0) + serializerByVersion[iSource[0]].deserializePartial((ODocument) iRecord, container, iFields); + else + serializerByVersion[iSource[0]].deserialize((ODocument) iRecord, container); + } catch (RuntimeException e) { + OLogManager.instance().warn(this, "Error deserializing record with id %s send this data for debugging: %s ", + iRecord.getIdentity().toString(), OBase64Utils.encodeBytes(iSource)); + throw e; + } + return iRecord; + } + + @Override + public byte[] toStream(final ORecord iSource, final boolean iOnlyDelta) { + checkTypeODocument(iSource); + + final BytesContainer container = new BytesContainer(); + + // WRITE SERIALIZER VERSION + int pos = container.alloc(1); + container.bytes[pos] = CURRENT_RECORD_VERSION; + // SERIALIZE RECORD + serializerByVersion[CURRENT_RECORD_VERSION].serialize((ODocument) iSource, container, false); + + return container.fitBytes(); + } + + @Override + public String[] getFieldNames(ODocument reference, final byte[] iSource) { + if (iSource == null || iSource.length == 0) + return new String[0]; + + final BytesContainer container = new BytesContainer(iSource).skip(1); + + try { + return serializerByVersion[iSource[0]].getFieldNames(reference, container); + } catch (RuntimeException e) { + OLogManager.instance().warn(this, "Error deserializing record to get field-names, send this data for debugging: %s ", + OBase64Utils.encodeBytes(iSource)); + throw e; + } + } + + private void checkTypeODocument(final ORecord iRecord) { + if (!(iRecord instanceof ODocument)) { + throw new UnsupportedOperationException("The " + ORecordSerializerBinary.NAME + " don't support record of type " + + iRecord.getClass().getName()); + } + } + + public byte[] writeClassOnly(ORecord iSource) { + final BytesContainer container = new BytesContainer(); + + // WRITE SERIALIZER VERSION + int pos = container.alloc(1); + container.bytes[pos] = CURRENT_RECORD_VERSION; + + // SERIALIZE CLASS ONLY + serializerByVersion[CURRENT_RECORD_VERSION].serialize((ODocument) iSource, container, true); + + return container.fitBytes(); + } + + @Override + public boolean getSupportBinaryEvaluate() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryDebug.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryDebug.java new file mode 100755 index 00000000000..e1dbd5e7635 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryDebug.java @@ -0,0 +1,99 @@ +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import java.util.ArrayList; + +import com.orientechnologies.common.exception.OSystemException; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OGlobalProperty; +import com.orientechnologies.orient.core.metadata.schema.OImmutableSchema; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class ORecordSerializerBinaryDebug extends ORecordSerializerBinaryV0 { + + public ORecordSerializationDebug deserializeDebug(final byte[] iSource, ODatabaseDocumentInternal db) { + ORecordSerializationDebug debugInfo = new ORecordSerializationDebug(); + OImmutableSchema schema = ((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot(); + BytesContainer bytes = new BytesContainer(iSource); + if (bytes.bytes[0] != 0) + throw new OSystemException("Unsupported binary serialization version"); + bytes.skip(1); + try { + final String className = readString(bytes); + debugInfo.className = className; + } catch (RuntimeException ex) { + debugInfo.readingFailure = true; + debugInfo.readingException = ex; + debugInfo.failPosition = bytes.offset; + return debugInfo; + } + + debugInfo.properties = new ArrayList(); + int last = 0; + String fieldName; + int valuePos; + OType type; + while (true) { + ORecordSerializationDebugProperty debugProperty = new ORecordSerializationDebugProperty(); + OGlobalProperty prop = null; + try { + final int len = OVarIntSerializer.readAsInteger(bytes); + if (len != 0) + debugInfo.properties.add(debugProperty); + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // PARSE FIELD NAME + fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern(); + bytes.skip(len); + valuePos = readInteger(bytes); + type = readOType(bytes); + } else { + // LOAD GLOBAL PROPERTY BY ID + final int id = (len * -1) - 1; + debugProperty.globalId = id; + prop = schema.getGlobalPropertyById(id); + valuePos = readInteger(bytes); + debugProperty.valuePos = valuePos; + if (prop != null) { + fieldName = prop.getName(); + if (prop.getType() != OType.ANY) + type = prop.getType(); + else + type = readOType(bytes); + } else { + continue; + } + } + debugProperty.name = fieldName; + debugProperty.type = type; + + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + try { + debugProperty.value = deserializeValue(bytes, type, new ODocument()); + } catch (RuntimeException ex) { + debugProperty.faildToRead = true; + debugProperty.readingException = ex; + debugProperty.failPosition = bytes.offset; + } + if (bytes.offset > last) + last = bytes.offset; + bytes.offset = headerCursor; + } else + debugProperty.value = null; + } catch (RuntimeException ex) { + debugInfo.readingFailure = true; + debugInfo.readingException = ex; + debugInfo.failPosition = bytes.offset; + return debugInfo; + } + } + + return debugInfo; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryV0.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryV0.java new file mode 100755 index 00000000000..6fa01ea7944 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerBinaryV0.java @@ -0,0 +1,1014 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.serialization.types.ODecimalSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.exception.OValidationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.*; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentEntry; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.*; +import java.util.Map.Entry; + +public class ORecordSerializerBinaryV0 implements ODocumentSerializer { + + private static final String CHARSET_UTF_8 = "UTF-8"; + private static final ORecordId NULL_RECORD_ID = new ORecordId(-2, ORID.CLUSTER_POS_INVALID); + protected static final long MILLISEC_PER_DAY = 86400000; + + private final OBinaryComparatorV0 comparator = new OBinaryComparatorV0(); + + public ORecordSerializerBinaryV0() { + } + + public OBinaryComparator getComparator() { + return comparator; + } + + public void deserializePartial(final ODocument document, final BytesContainer bytes, final String[] iFields) { + final String className = readString(bytes); + if (className.length() != 0) + ODocumentInternal.fillClassNameIfNeeded(document, className); + + // TRANSFORMS FIELDS FOM STRINGS TO BYTE[] + final byte[][] fields = new byte[iFields.length][]; + for (int i = 0; i < iFields.length; ++i) + fields[i] = iFields[i].getBytes(); + + String fieldName = null; + int valuePos; + OType type; + int unmarshalledFields = 0; + + while (true) { + final int len = OVarIntSerializer.readAsInteger(bytes); + + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME + boolean match = false; + for (int i = 0; i < iFields.length; ++i) { + if (iFields[i] != null && iFields[i].length() == len) { + boolean matchField = true; + for (int j = 0; j < len; ++j) { + if (bytes.bytes[bytes.offset + j] != fields[i][j]) { + matchField = false; + break; + } + } + if (matchField) { + fieldName = iFields[i]; + bytes.skip(len); + match = true; + break; + } + } + } + + if (!match) { + // FIELD NOT INCLUDED: SKIP IT + bytes.skip(len + OIntegerSerializer.INT_SIZE + 1); + continue; + } + valuePos = readInteger(bytes); + type = readOType(bytes); + } else { + // LOAD GLOBAL PROPERTY BY ID + final OGlobalProperty prop = getGlobalProperty(document, len); + fieldName = prop.getName(); + + boolean matchField = false; + for (String f : iFields) { + if (fieldName.equals(f)) { + matchField = true; + break; + } + } + + if (!matchField) { + // FIELD NOT INCLUDED: SKIP IT + bytes.skip(OIntegerSerializer.INT_SIZE + (prop.getType() != OType.ANY ? 0 : 1)); + continue; + } + + valuePos = readInteger(bytes); + if (prop.getType() != OType.ANY) + type = prop.getType(); + else + type = readOType(bytes); + } + + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + final Object value = deserializeValue(bytes, type, document); + bytes.offset = headerCursor; + ODocumentInternal.rawField(document, fieldName, value, type); + } else + ODocumentInternal.rawField(document, fieldName, null, null); + + if (++unmarshalledFields == iFields.length) + // ALL REQUESTED FIELDS UNMARSHALLED: EXIT + break; + } + } + + public OBinaryField deserializeField(final BytesContainer bytes, final OClass iClass, final String iFieldName) { + // SKIP CLASS NAME + final int classNameLen = OVarIntSerializer.readAsInteger(bytes); + bytes.skip(classNameLen); + + final byte[] field = iFieldName.getBytes(); + + final OMetadataInternal metadata = (OMetadataInternal) ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata(); + final OImmutableSchema _schema = metadata.getImmutableSchemaSnapshot(); + + while (true) { + final int len = OVarIntSerializer.readAsInteger(bytes); + + if (len == 0) { + // SCAN COMPLETED, NO FIELD FOUND + return null; + } else if (len > 0) { + // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME + if (iFieldName.length() == len) { + boolean match = true; + for (int j = 0; j < len; ++j) + if (bytes.bytes[bytes.offset + j] != field[j]) { + match = false; + break; + } + + bytes.skip(len); + final int valuePos = readInteger(bytes); + final OType type = readOType(bytes); + + if (valuePos == 0) + return null; + + if (!match) + continue; + + if (!ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isBinaryComparable(type)) + return null; + + bytes.offset = valuePos; + return new OBinaryField(iFieldName, type, bytes, null); + } + + // SKIP IT + bytes.skip(len + OIntegerSerializer.INT_SIZE + 1); + continue; + + } else { + // LOAD GLOBAL PROPERTY BY ID + final int id = (len * -1) - 1; + final OGlobalProperty prop = _schema.getGlobalPropertyById(id); + if (iFieldName.equals(prop.getName())) { + final int valuePos = readInteger(bytes); + final OType type; + if (prop.getType() != OType.ANY) + type = prop.getType(); + else + type = readOType(bytes); + + if (valuePos == 0) + return null; + + if (!ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isBinaryComparable(type)) + return null; + + bytes.offset = valuePos; + + final OProperty classProp = iClass.getProperty(iFieldName); + return new OBinaryField(iFieldName, type, bytes, classProp != null ? classProp.getCollate() : null); + } + bytes.skip(OIntegerSerializer.INT_SIZE + (prop.getType() != OType.ANY ? 0 : 1)); + } + } + } + + @Override + public void deserialize(final ODocument document, final BytesContainer bytes) { + final String className = readString(bytes); + if (className.length() != 0) + ODocumentInternal.fillClassNameIfNeeded(document, className); + + int last = 0; + String fieldName; + int valuePos; + OType type; + while (true) { + OGlobalProperty prop = null; + final int len = OVarIntSerializer.readAsInteger(bytes); + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // PARSE FIELD NAME + fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern(); + bytes.skip(len); + valuePos = readInteger(bytes); + type = readOType(bytes); + } else { + // LOAD GLOBAL PROPERTY BY ID + prop = getGlobalProperty(document, len); + if (prop == null) + throw new OSerializationException("Missing property definition for property id '" + ((len * -1) - 1) + "'"); + fieldName = prop.getName(); + valuePos = readInteger(bytes); + if (prop.getType() != OType.ANY) + type = prop.getType(); + else + type = readOType(bytes); + } + + if (ODocumentInternal.rawContainsField(document, fieldName)) { + continue; + } + + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + final Object value = deserializeValue(bytes, type, document); + if (bytes.offset > last) + last = bytes.offset; + bytes.offset = headerCursor; + ODocumentInternal.rawField(document, fieldName, value, type); + } else + ODocumentInternal.rawField(document, fieldName, null, null); + } + + ORecordInternal.clearSource(document); + + if (last > bytes.offset) + bytes.offset = last; + } + + @Override + public String[] getFieldNames(ODocument reference, final BytesContainer bytes) { + // SKIP CLASS NAME + final int classNameLen = OVarIntSerializer.readAsInteger(bytes); + bytes.skip(classNameLen); + + final List result = new ArrayList(); + + String fieldName; + while (true) { + OGlobalProperty prop = null; + final int len = OVarIntSerializer.readAsInteger(bytes); + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // PARSE FIELD NAME + fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern(); + result.add(fieldName); + + // SKIP THE REST + bytes.skip(len + OIntegerSerializer.INT_SIZE + 1); + } else { + // LOAD GLOBAL PROPERTY BY ID + final int id = (len * -1) - 1; + prop = ODocumentInternal.getGlobalPropertyById(reference, id); + if (prop == null) { + throw new OSerializationException("Missing property definition for property id '" + id + "'"); + } + result.add(prop.getName()); + + // SKIP THE REST + bytes.skip(OIntegerSerializer.INT_SIZE + (prop.getType() != OType.ANY ? 0 : 1)); + } + } + + return result.toArray(new String[result.size()]); + } + + @SuppressWarnings("unchecked") + @Override + public void serialize(final ODocument document, final BytesContainer bytes, final boolean iClassOnly) { + + final OClass clazz = serializeClass(document, bytes); + if (iClassOnly) { + writeEmptyString(bytes); + return; + } + + final Map props = clazz != null ? clazz.propertiesMap() : null; + + final Set> fields = ODocumentInternal.rawEntries(document); + + final int[] pos = new int[fields.size()]; + + int i = 0; + + final Entry values[] = new Entry[fields.size()]; + for (Entry entry : fields) { + ODocumentEntry docEntry = entry.getValue(); + if (!docEntry.exist()) + continue; + if (docEntry.property == null && props != null) { + OProperty prop = props.get(entry.getKey()); + if (prop != null && docEntry.type == prop.getType()) + docEntry.property = prop; + } + + if (docEntry.property != null) { + OVarIntSerializer.write(bytes, (docEntry.property.getId() + 1) * -1); + if (docEntry.property.getType() != OType.ANY) + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE); + else + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); + } else { + writeString(bytes, entry.getKey()); + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); + } + values[i] = entry; + i++; + } + writeEmptyString(bytes); + int size = i; + + for (i = 0; i < size; i++) { + int pointer = 0; + final Object value = values[i].getValue().value; + if (value != null) { + final OType type = getFieldType(values[i].getValue()); + if (type == null) { + throw new OSerializationException( + "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer"); + } + pointer = serializeValue(bytes, value, type, getLinkedType(document, type, values[i].getKey())); + OIntegerSerializer.INSTANCE.serializeLiteral(pointer, bytes.bytes, pos[i]); + if (values[i].getValue().property == null || values[i].getValue().property.getType() == OType.ANY) + writeOType(bytes, (pos[i] + OIntegerSerializer.INT_SIZE), type); + } + } + + } + + @Override + public Object deserializeValue(final BytesContainer bytes, final OType type, final ODocument ownerDocument) { + Object value = null; + switch (type) { + case INTEGER: + value = OVarIntSerializer.readAsInteger(bytes); + break; + case LONG: + value = OVarIntSerializer.readAsLong(bytes); + break; + case SHORT: + value = OVarIntSerializer.readAsShort(bytes); + break; + case STRING: + value = readString(bytes); + break; + case DOUBLE: + value = Double.longBitsToDouble(readLong(bytes)); + break; + case FLOAT: + value = Float.intBitsToFloat(readInteger(bytes)); + break; + case BYTE: + value = readByte(bytes); + break; + case BOOLEAN: + value = readByte(bytes) == 1; + break; + case DATETIME: + value = new Date(OVarIntSerializer.readAsLong(bytes)); + break; + case DATE: + long savedTime = OVarIntSerializer.readAsLong(bytes) * MILLISEC_PER_DAY; + savedTime = convertDayToTimezone(TimeZone.getTimeZone("GMT"), ODateHelper.getDatabaseTimeZone(), savedTime); + value = new Date(savedTime); + break; + case EMBEDDED: + value = new ODocument(); + deserialize((ODocument) value, bytes); + if (((ODocument) value).containsField(ODocumentSerializable.CLASS_NAME)) { + String className = ((ODocument) value).field(ODocumentSerializable.CLASS_NAME); + try { + Class clazz = Class.forName(className); + ODocumentSerializable newValue = (ODocumentSerializable) clazz.newInstance(); + newValue.fromDocument((ODocument) value); + value = newValue; + } catch (Exception e) { + throw new RuntimeException(e); + } + } else + ODocumentInternal.addOwner((ODocument) value, ownerDocument); + + break; + case EMBEDDEDSET: + value = readEmbeddedSet(bytes, ownerDocument); + break; + case EMBEDDEDLIST: + value = readEmbeddedList(bytes, ownerDocument); + break; + case LINKSET: + value = readLinkCollection(bytes, new ORecordLazySet(ownerDocument)); + break; + case LINKLIST: + value = readLinkCollection(bytes, new ORecordLazyList(ownerDocument)); + break; + case BINARY: + value = readBinary(bytes); + break; + case LINK: + value = readOptimizedLink(bytes); + break; + case LINKMAP: + value = readLinkMap(bytes, ownerDocument); + break; + case EMBEDDEDMAP: + value = readEmbeddedMap(bytes, ownerDocument); + break; + case DECIMAL: + value = ODecimalSerializer.INSTANCE.deserialize(bytes.bytes, bytes.offset); + bytes.skip(ODecimalSerializer.INSTANCE.getObjectSize(bytes.bytes, bytes.offset)); + break; + case LINKBAG: + ORidBag bag = new ORidBag(); + bag.fromStream(bytes); + bag.setOwner(ownerDocument); + value = bag; + break; + case TRANSIENT: + break; + case CUSTOM: + try { + String className = readString(bytes); + Class clazz = Class.forName(className); + OSerializableStream stream = (OSerializableStream) clazz.newInstance(); + stream.fromStream(readBinary(bytes)); + if (stream instanceof OSerializableWrapper) + value = ((OSerializableWrapper) stream).getSerializable(); + else + value = stream; + } catch (Exception e) { + throw new RuntimeException(e); + } + break; + case ANY: + break; + + } + return value; + } + + protected OClass serializeClass(final ODocument document, final BytesContainer bytes) { + final OClass clazz = ODocumentInternal.getImmutableSchemaClass(document); + if (clazz != null) + writeString(bytes, clazz.getName()); + else + writeEmptyString(bytes); + return clazz; + } + + protected OGlobalProperty getGlobalProperty(final ODocument document, final int len) { + final int id = (len * -1) - 1; + return ODocumentInternal.getGlobalPropertyById(document, id); + } + + protected OType readOType(final BytesContainer bytes) { + return OType.getById(readByte(bytes)); + } + + private void writeOType(BytesContainer bytes, int pos, OType type) { + bytes.bytes[pos] = (byte) type.getId(); + } + + private static byte[] readBinary(final BytesContainer bytes) { + final int n = OVarIntSerializer.readAsInteger(bytes); + final byte[] newValue = new byte[n]; + System.arraycopy(bytes.bytes, bytes.offset, newValue, 0, newValue.length); + bytes.skip(n); + return newValue; + } + + private Map readLinkMap(final BytesContainer bytes, final ODocument document) { + int size = OVarIntSerializer.readAsInteger(bytes); + final ORecordLazyMap result = new ORecordLazyMap(document); + + result.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + + while ((size--) > 0) { + final OType keyType = readOType(bytes); + final Object key = deserializeValue(bytes, keyType, document); + final ORecordId value = readOptimizedLink(bytes); + if (value.equals(NULL_RECORD_ID)) + result.put(key, null); + else + result.put(key, value); + } + return result; + + } finally { + result.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + + private Object readEmbeddedMap(final BytesContainer bytes, final ODocument document) { + int size = OVarIntSerializer.readAsInteger(bytes); + final OTrackedMap result = new OTrackedMap(document); + + result.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + + int last = 0; + while ((size--) > 0) { + OType keyType = readOType(bytes); + Object key = deserializeValue(bytes, keyType, document); + final int valuePos = readInteger(bytes); + final OType type = readOType(bytes); + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + Object value = deserializeValue(bytes, type, document); + if (bytes.offset > last) + last = bytes.offset; + bytes.offset = headerCursor; + result.put(key, value); + } else + result.put(key, null); + } + if (last > bytes.offset) + bytes.offset = last; + return result; + + } finally { + result.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + + private Collection readLinkCollection(final BytesContainer bytes, final Collection found) { + final int items = OVarIntSerializer.readAsInteger(bytes); + for (int i = 0; i < items; i++) { + ORecordId id = readOptimizedLink(bytes); + if (id.equals(NULL_RECORD_ID)) + found.add(null); + else + found.add(id); + } + return found; + } + + protected static ORecordId readOptimizedLink(final BytesContainer bytes) { + return new ORecordId(OVarIntSerializer.readAsInteger(bytes), OVarIntSerializer.readAsLong(bytes)); + } + + private Collection readEmbeddedSet(final BytesContainer bytes, final ODocument ownerDocument) { + + final int items = OVarIntSerializer.readAsInteger(bytes); + OType type = readOType(bytes); + + if (type == OType.ANY) { + final OTrackedSet found = new OTrackedSet(ownerDocument); + + found.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + + for (int i = 0; i < items; i++) { + OType itemType = readOType(bytes); + if (itemType == OType.ANY) + found.add(null); + else + found.add(deserializeValue(bytes, itemType, ownerDocument)); + } + return found; + + } finally { + found.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + // TODO: manage case where type is known + return null; + } + + private Collection readEmbeddedList(final BytesContainer bytes, final ODocument ownerDocument) { + + final int items = OVarIntSerializer.readAsInteger(bytes); + OType type = readOType(bytes); + + if (type == OType.ANY) { + final OTrackedList found = new OTrackedList(ownerDocument); + + found.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING); + try { + + for (int i = 0; i < items; i++) { + OType itemType = readOType(bytes); + if (itemType == OType.ANY) + found.add(null); + else + found.add(deserializeValue(bytes, itemType, ownerDocument)); + } + return found; + + } finally { + found.setInternalStatus(ORecordElement.STATUS.LOADED); + } + } + // TODO: manage case where type is known + return null; + } + + private OType getLinkedType(ODocument document, OType type, String key) { + if (type != OType.EMBEDDEDLIST && type != OType.EMBEDDEDSET && type != OType.EMBEDDEDMAP) + return null; + OClass immutableClass = ODocumentInternal.getImmutableSchemaClass(document); + if (immutableClass != null) { + OProperty prop = immutableClass.getProperty(key); + if (prop != null) { + return prop.getLinkedType(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + public int serializeValue(final BytesContainer bytes, Object value, final OType type, final OType linkedType) { + int pointer = 0; + switch (type) { + case INTEGER: + case LONG: + case SHORT: + pointer = OVarIntSerializer.write(bytes, ((Number) value).longValue()); + break; + case STRING: + pointer = writeString(bytes, value.toString()); + break; + case DOUBLE: + long dg = Double.doubleToLongBits(((Number) value).doubleValue()); + pointer = bytes.alloc(OLongSerializer.LONG_SIZE); + OLongSerializer.INSTANCE.serializeLiteral(dg, bytes.bytes, pointer); + break; + case FLOAT: + int fg = Float.floatToIntBits(((Number) value).floatValue()); + pointer = bytes.alloc(OIntegerSerializer.INT_SIZE); + OIntegerSerializer.INSTANCE.serializeLiteral(fg, bytes.bytes, pointer); + break; + case BYTE: + pointer = bytes.alloc(1); + bytes.bytes[pointer] = ((Number) value).byteValue(); + break; + case BOOLEAN: + pointer = bytes.alloc(1); + bytes.bytes[pointer] = ((Boolean) value) ? (byte) 1 : (byte) 0; + break; + case DATETIME: + if (value instanceof Number) { + pointer = OVarIntSerializer.write(bytes, ((Number) value).longValue()); + } else + pointer = OVarIntSerializer.write(bytes, ((Date) value).getTime()); + break; + case DATE: + long dateValue; + if (value instanceof Number) { + dateValue = ((Number) value).longValue(); + } else + dateValue = ((Date) value).getTime(); + dateValue = convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), dateValue); + pointer = OVarIntSerializer.write(bytes, dateValue / MILLISEC_PER_DAY); + break; + case EMBEDDED: + pointer = bytes.offset; + if (value instanceof ODocumentSerializable) { + ODocument cur = ((ODocumentSerializable) value).toDocument(); + cur.field(ODocumentSerializable.CLASS_NAME, value.getClass().getName()); + serialize(cur, bytes, false); + } else { + serialize((ODocument) value, bytes, false); + } + break; + case EMBEDDEDSET: + case EMBEDDEDLIST: + if (value.getClass().isArray()) + pointer = writeEmbeddedCollection(bytes, Arrays.asList(OMultiValue.array(value)), linkedType); + else + pointer = writeEmbeddedCollection(bytes, (Collection) value, linkedType); + break; + case DECIMAL: + BigDecimal decimalValue = (BigDecimal) value; + pointer = bytes.alloc(ODecimalSerializer.INSTANCE.getObjectSize(decimalValue)); + ODecimalSerializer.INSTANCE.serialize(decimalValue, bytes.bytes, pointer); + break; + case BINARY: + pointer = writeBinary(bytes, (byte[]) (value)); + break; + case LINKSET: + case LINKLIST: + Collection ridCollection = (Collection) value; + pointer = writeLinkCollection(bytes, ridCollection); + break; + case LINK: + if (!(value instanceof OIdentifiable)) + throw new OValidationException("Value '" + value + "' is not a OIdentifiable"); + + pointer = writeOptimizedLink(bytes, (OIdentifiable) value); + break; + case LINKMAP: + pointer = writeLinkMap(bytes, (Map) value); + break; + case EMBEDDEDMAP: + pointer = writeEmbeddedMap(bytes, (Map) value); + break; + case LINKBAG: + pointer = ((ORidBag) value).toStream(bytes); + break; + case CUSTOM: + if (!(value instanceof OSerializableStream)) + value = new OSerializableWrapper((Serializable) value); + pointer = writeString(bytes, value.getClass().getName()); + writeBinary(bytes, ((OSerializableStream) value).toStream()); + break; + case TRANSIENT: + break; + case ANY: + break; + } + return pointer; + } + + private int writeBinary(final BytesContainer bytes, final byte[] valueBytes) { + final int pointer = OVarIntSerializer.write(bytes, valueBytes.length); + final int start = bytes.alloc(valueBytes.length); + System.arraycopy(valueBytes, 0, bytes.bytes, start, valueBytes.length); + return pointer; + } + + private int writeLinkMap(final BytesContainer bytes, final Map map) { + final boolean disabledAutoConversion = + map instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) map).isAutoConvertToRecord(); + + if (disabledAutoConversion) + // AVOID TO FETCH RECORD + ((ORecordLazyMultiValue) map).setAutoConvertToRecord(false); + + try { + final int fullPos = OVarIntSerializer.write(bytes, map.size()); + for (Entry entry : map.entrySet()) { + // TODO:check skip of complex types + // FIXME: changed to support only string key on map + final OType type = OType.STRING; + writeOType(bytes, bytes.alloc(1), type); + writeString(bytes, entry.getKey().toString()); + if (entry.getValue() == null) + writeNullLink(bytes); + else + writeOptimizedLink(bytes, entry.getValue()); + } + return fullPos; + + } finally { + if (disabledAutoConversion) + ((ORecordLazyMultiValue) map).setAutoConvertToRecord(true); + } + } + + @SuppressWarnings("unchecked") + private int writeEmbeddedMap(BytesContainer bytes, Map map) { + final int[] pos = new int[map.size()]; + int i = 0; + Entry values[] = new Entry[map.size()]; + final int fullPos = OVarIntSerializer.write(bytes, map.size()); + for (Entry entry : map.entrySet()) { + // TODO:check skip of complex types + // FIXME: changed to support only string key on map + OType type = OType.STRING; + writeOType(bytes, bytes.alloc(1), type); + writeString(bytes, entry.getKey().toString()); + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); + values[i] = entry; + i++; + } + + for (i = 0; i < values.length; i++) { + int pointer = 0; + final Object value = values[i].getValue(); + if (value != null) { + final OType type = getTypeFromValueEmbedded(value); + if (type == null) { + throw new OSerializationException( + "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer"); + } + pointer = serializeValue(bytes, value, type, null); + OIntegerSerializer.INSTANCE.serializeLiteral(pointer, bytes.bytes, pos[i]); + writeOType(bytes, (pos[i] + OIntegerSerializer.INT_SIZE), type); + } + } + return fullPos; + } + + private int writeNullLink(final BytesContainer bytes) { + final int pos = OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterId()); + OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterPosition()); + return pos; + + } + + private int writeOptimizedLink(final BytesContainer bytes, OIdentifiable link) { + if (!link.getIdentity().isPersistent()) { + try { + final ORecord real = link.getRecord(); + if (real != null) + link = real; + } catch (ORecordNotFoundException ex) { + // IGNORE IT WILL FAIL THE ASSERT IN CASE + } + } + if (link.getIdentity().getClusterId() < 0 && ORecordSerializationContext.getContext() != null) + throw new ODatabaseException("Impossible to serialize invalid link " + link.getIdentity()); + + final int pos = OVarIntSerializer.write(bytes, link.getIdentity().getClusterId()); + OVarIntSerializer.write(bytes, link.getIdentity().getClusterPosition()); + return pos; + } + + private int writeLinkCollection(final BytesContainer bytes, final Collection value) { + final int pos = OVarIntSerializer.write(bytes, value.size()); + + final boolean disabledAutoConversion = + value instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) value).isAutoConvertToRecord(); + + if (disabledAutoConversion) + // AVOID TO FETCH RECORD + ((ORecordLazyMultiValue) value).setAutoConvertToRecord(false); + + try { + for (OIdentifiable itemValue : value) { + // TODO: handle the null links + if (itemValue == null) + writeNullLink(bytes); + else + writeOptimizedLink(bytes, itemValue); + } + + } finally { + if (disabledAutoConversion) + ((ORecordLazyMultiValue) value).setAutoConvertToRecord(true); + } + + return pos; + } + + private int writeEmbeddedCollection(final BytesContainer bytes, final Collection value, final OType linkedType) { + final int pos = OVarIntSerializer.write(bytes, value.size()); + // TODO manage embedded type from schema and auto-determined. + writeOType(bytes, bytes.alloc(1), OType.ANY); + for (Object itemValue : value) { + // TODO:manage in a better way null entry + if (itemValue == null) { + writeOType(bytes, bytes.alloc(1), OType.ANY); + continue; + } + OType type; + if (linkedType == null || linkedType == OType.ANY) + type = getTypeFromValueEmbedded(itemValue); + else + type = linkedType; + if (type != null) { + writeOType(bytes, bytes.alloc(1), type); + serializeValue(bytes, itemValue, type, null); + } else { + throw new OSerializationException( + "Impossible serialize value of type " + itemValue.getClass() + " with the ODocument binary serializer"); + } + } + return pos; + } + + private OType getFieldType(final ODocumentEntry entry) { + OType type = entry.type; + if (type == null) { + final OProperty prop = entry.property; + if (prop != null) + type = prop.getType(); + + } + if (type == null || OType.ANY == type) + type = OType.getTypeByValue(entry.value); + return type; + } + + private OType getTypeFromValueEmbedded(final Object fieldValue) { + OType type = OType.getTypeByValue(fieldValue); + if (type == OType.LINK && fieldValue instanceof ODocument && !((ODocument) fieldValue).getIdentity().isValid()) + type = OType.EMBEDDED; + return type; + } + + protected static String readString(final BytesContainer bytes) { + final int len = OVarIntSerializer.readAsInteger(bytes); + final String res = stringFromBytes(bytes.bytes, bytes.offset, len); + bytes.skip(len); + return res; + } + + protected static int readInteger(final BytesContainer container) { + final int value = OIntegerSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset); + container.offset += OIntegerSerializer.INT_SIZE; + return value; + } + + protected static byte readByte(final BytesContainer container) { + return container.bytes[container.offset++]; + } + + protected static long readLong(final BytesContainer container) { + final long value = OLongSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset); + container.offset += OLongSerializer.LONG_SIZE; + return value; + } + + private int writeEmptyString(final BytesContainer bytes) { + return OVarIntSerializer.write(bytes, 0); + } + + private int writeString(final BytesContainer bytes, final String toWrite) { + final byte[] nameBytes = bytesFromString(toWrite); + final int pointer = OVarIntSerializer.write(bytes, nameBytes.length); + final int start = bytes.alloc(nameBytes.length); + System.arraycopy(nameBytes, 0, bytes.bytes, start, nameBytes.length); + return pointer; + } + + private byte[] bytesFromString(final String toWrite) { + try { + return toWrite.getBytes(CHARSET_UTF_8); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error on string encoding"), e); + } + } + + protected static String stringFromBytes(final byte[] bytes, final int offset, final int len) { + try { + return new String(bytes, offset, len, CHARSET_UTF_8); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error on string decoding"), e); + } + } + + protected static long convertDayToTimezone(TimeZone from, TimeZone to, long time) { + Calendar fromCalendar = Calendar.getInstance(from); + fromCalendar.setTimeInMillis(time); + Calendar toCalendar = Calendar.getInstance(to); + toCalendar.setTimeInMillis(0); + toCalendar.set(Calendar.ERA, fromCalendar.get(Calendar.ERA)); + toCalendar.set(Calendar.YEAR, fromCalendar.get(Calendar.YEAR)); + toCalendar.set(Calendar.MONTH, fromCalendar.get(Calendar.MONTH)); + toCalendar.set(Calendar.DAY_OF_MONTH, fromCalendar.get(Calendar.DAY_OF_MONTH)); + toCalendar.set(Calendar.HOUR_OF_DAY, 0); + toCalendar.set(Calendar.MINUTE, 0); + toCalendar.set(Calendar.SECOND, 0); + toCalendar.set(Calendar.MILLISECOND, 0); + return toCalendar.getTimeInMillis(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetwork.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetwork.java new file mode 100644 index 00000000000..68bb8da9057 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetwork.java @@ -0,0 +1,137 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; + +public class ORecordSerializerNetwork implements ORecordSerializer { + + public static final String NAME = "onet_ser_v0"; + public static final ORecordSerializerNetwork INSTANCE = new ORecordSerializerNetwork(); + private static final byte CURRENT_RECORD_VERSION = 0; + + private ODocumentSerializer[] serializerByVersion; + + public ORecordSerializerNetwork() { + serializerByVersion = new ODocumentSerializer[1]; + serializerByVersion[0] = new ORecordSerializerNetworkV0(); + } + + @Override + public int getCurrentVersion() { + return CURRENT_RECORD_VERSION; + } + + @Override + public int getMinSupportedVersion() { + return CURRENT_RECORD_VERSION; + } + + @Override + public String toString() { + return NAME; + } + + @Override + public ORecord fromStream(final byte[] iSource, ORecord iRecord, final String[] iFields) { + if (iSource == null || iSource.length == 0) + return iRecord; + if (iRecord == null) + iRecord = new ODocument(); + else + checkTypeODocument(iRecord); + + BytesContainer container = new BytesContainer(iSource); + container.skip(1); + + try { + if (iFields != null && iFields.length > 0) + serializerByVersion[iSource[0]].deserializePartial((ODocument) iRecord, container, iFields); + else + serializerByVersion[iSource[0]].deserialize((ODocument) iRecord, container); + } catch (RuntimeException e) { + OLogManager.instance().warn(this, "Error deserializing record with id %s send this data for debugging: %s ", + iRecord.getIdentity().toString(), OBase64Utils.encodeBytes(iSource)); + throw e; + } + return iRecord; + } + + @Override + public byte[] toStream(final ORecord iSource, final boolean iOnlyDelta) { + checkTypeODocument(iSource); + + final BytesContainer container = new BytesContainer(); + + // WRITE SERIALIZER VERSION + int pos = container.alloc(1); + container.bytes[pos] = CURRENT_RECORD_VERSION; + // SERIALIZE RECORD + serializerByVersion[CURRENT_RECORD_VERSION].serialize((ODocument) iSource, container, false); + + return container.fitBytes(); + } + + @Override + public String[] getFieldNames(ODocument reference, final byte[] iSource) { + if (iSource == null || iSource.length == 0) + return new String[0]; + + final BytesContainer container = new BytesContainer(iSource).skip(1); + + try { + return serializerByVersion[iSource[0]].getFieldNames(reference, container); + } catch (RuntimeException e) { + OLogManager.instance().warn(this, "Error deserializing record to get field-names, send this data for debugging: %s ", + OBase64Utils.encodeBytes(iSource)); + throw e; + } + } + + private void checkTypeODocument(final ORecord iRecord) { + if (!(iRecord instanceof ODocument)) { + throw new UnsupportedOperationException("The " + ORecordSerializerNetwork.NAME + " don't support record of type " + + iRecord.getClass().getName()); + } + } + + public byte[] writeClassOnly(ORecord iSource) { + final BytesContainer container = new BytesContainer(); + + // WRITE SERIALIZER VERSION + int pos = container.alloc(1); + container.bytes[pos] = CURRENT_RECORD_VERSION; + + // SERIALIZE CLASS ONLY + serializerByVersion[CURRENT_RECORD_VERSION].serialize((ODocument) iSource, container, true); + + return container.fitBytes(); + } + + public boolean getSupportBinaryEvaluate() { + return false; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetworkV0.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetworkV0.java new file mode 100644 index 00000000000..6eb0dfbf21f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/ORecordSerializerNetworkV0.java @@ -0,0 +1,839 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.*; +import java.util.Map.Entry; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.serialization.types.ODecimalSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyList; +import com.orientechnologies.orient.core.db.record.ORecordLazyMap; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.db.record.OTrackedList; +import com.orientechnologies.orient.core.db.record.OTrackedMap; +import com.orientechnologies.orient.core.db.record.OTrackedSet; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.exception.OStorageException; +import com.orientechnologies.orient.core.exception.OValidationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OGlobalProperty; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentEntry; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.util.ODateHelper; + +public class ORecordSerializerNetworkV0 implements ODocumentSerializer { + + private static final String CHARSET_UTF_8 = "UTF-8"; + private static final ORecordId NULL_RECORD_ID = new ORecordId(-2, ORID.CLUSTER_POS_INVALID); + private static final long MILLISEC_PER_DAY = 86400000; + + public ORecordSerializerNetworkV0() { + } + + public void deserializePartial(final ODocument document, final BytesContainer bytes, final String[] iFields) { + final String className = readString(bytes); + if (className.length() != 0) + ODocumentInternal.fillClassNameIfNeeded(document, className); + + // TRANSFORMS FIELDS FOM STRINGS TO BYTE[] + final byte[][] fields = new byte[iFields.length][]; + for (int i = 0; i < iFields.length; ++i) + fields[i] = iFields[i].getBytes(); + + String fieldName = null; + int valuePos; + OType type; + int unmarshalledFields = 0; + + while (true) { + final int len = OVarIntSerializer.readAsInteger(bytes); + + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME + boolean match = false; + for (int i = 0; i < iFields.length; ++i) { + if (iFields[i].length() == len) { + boolean matchField = true; + for (int j = 0; j < len; ++j) { + if (bytes.bytes[bytes.offset + j] != fields[i][j]) { + matchField = false; + break; + } + } + if (matchField) { + fieldName = iFields[i]; + unmarshalledFields++; + bytes.skip(len); + match = true; + break; + } + } + } + + if (!match) { + // SKIP IT + bytes.skip(len + OIntegerSerializer.INT_SIZE + 1); + continue; + } + valuePos = readInteger(bytes); + type = readOType(bytes); + } else { + throw new OStorageException("property id not supported in network serialization"); + } + + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + final Object value = deserializeValue(bytes, type, document); + bytes.offset = headerCursor; + document.field(fieldName, value, type); + } else + document.field(fieldName, null, null); + + if (unmarshalledFields == iFields.length) + // ALL REQUESTED FIELDS UNMARSHALLED: EXIT + break; + } + } + + @Override + public void deserialize(final ODocument document, final BytesContainer bytes) { + final String className = readString(bytes); + if (className.length() != 0) + ODocumentInternal.fillClassNameIfNeeded(document, className); + + int last = 0; + String fieldName; + int valuePos; + OType type; + while (true) { + final int len = OVarIntSerializer.readAsInteger(bytes); + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // PARSE FIELD NAME + fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern(); + bytes.skip(len); + valuePos = readInteger(bytes); + type = readOType(bytes); + } else { + throw new OStorageException("property id not supported in network serialization"); + } + + if (ODocumentInternal.rawContainsField(document, fieldName)) { + continue; + } + + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + final Object value = deserializeValue(bytes, type, document); + if (bytes.offset > last) + last = bytes.offset; + bytes.offset = headerCursor; + document.field(fieldName, value, type); + } else + document.field(fieldName, null, null); + } + + ORecordInternal.clearSource(document); + + if (last > bytes.offset) + bytes.offset = last; + } + + @SuppressWarnings("unchecked") + @Override + public void serialize(final ODocument document, final BytesContainer bytes, final boolean iClassOnly) { + + final OClass clazz = serializeClass(document, bytes); + if (iClassOnly) { + writeEmptyString(bytes); + return; + } + + final Set> fields = ODocumentInternal.rawEntries(document); + + final int[] pos = new int[fields.size()]; + + int i = 0; + + final Entry values[] = new Entry[fields.size()]; + for (Entry entry : fields) { + ODocumentEntry docEntry = entry.getValue(); + if (!docEntry.exist()) + continue; + writeString(bytes, entry.getKey()); + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); + values[i] = entry; + i++; + } + writeEmptyString(bytes); + int size = i; + + for (i = 0; i < size; i++) { + int pointer = 0; + final Object value = values[i].getValue().value; + if (value != null) { + final OType type = getFieldType(values[i].getValue()); + if (type == null) { + throw new OSerializationException( + "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer"); + } + pointer = serializeValue(bytes, value, type, getLinkedType(document, type, values[i].getKey())); + OIntegerSerializer.INSTANCE.serializeLiteral(pointer, bytes.bytes, pos[i]); + writeOType(bytes, (pos[i] + OIntegerSerializer.INT_SIZE), type); + } + } + + } + + @Override + public String[] getFieldNames(ODocument reference, final BytesContainer bytes) { + // SKIP CLASS NAME + final int classNameLen = OVarIntSerializer.readAsInteger(bytes); + bytes.skip(classNameLen); + + final List result = new ArrayList(); + + String fieldName; + while (true) { + OGlobalProperty prop = null; + final int len = OVarIntSerializer.readAsInteger(bytes); + if (len == 0) { + // SCAN COMPLETED + break; + } else if (len > 0) { + // PARSE FIELD NAME + fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern(); + result.add(fieldName); + + // SKIP THE REST + bytes.skip(len + OIntegerSerializer.INT_SIZE + 1); + } else { + // LOAD GLOBAL PROPERTY BY ID + final int id = (len * -1) - 1; + prop = ODocumentInternal.getGlobalPropertyById(reference, id); + result.add(prop.getName()); + + // SKIP THE REST + bytes.skip(OIntegerSerializer.INT_SIZE + (prop.getType() != OType.ANY ? 0 : 1)); + } + } + + return result.toArray(new String[result.size()]); + } + + protected OClass serializeClass(final ODocument document, final BytesContainer bytes) { + final OClass clazz = ODocumentInternal.getImmutableSchemaClass(document); + String name = null; + if (clazz != null) + name = clazz.getName(); + if (name == null) + name = document.getClassName(); + + if (name != null) + writeString(bytes, name); + else + writeEmptyString(bytes); + return clazz; + } + + protected OType readOType(final BytesContainer bytes) { + return OType.getById(readByte(bytes)); + } + + private void writeOType(BytesContainer bytes, int pos, OType type) { + bytes.bytes[pos] = (byte) type.getId(); + } + + public Object deserializeValue(BytesContainer bytes, OType type, ODocument document) { + Object value = null; + switch (type) { + case INTEGER: + value = OVarIntSerializer.readAsInteger(bytes); + break; + case LONG: + value = OVarIntSerializer.readAsLong(bytes); + break; + case SHORT: + value = OVarIntSerializer.readAsShort(bytes); + break; + case STRING: + value = readString(bytes); + break; + case DOUBLE: + value = Double.longBitsToDouble(readLong(bytes)); + break; + case FLOAT: + value = Float.intBitsToFloat(readInteger(bytes)); + break; + case BYTE: + value = readByte(bytes); + break; + case BOOLEAN: + value = readByte(bytes) == 1; + break; + case DATETIME: + value = new Date(OVarIntSerializer.readAsLong(bytes)); + break; + case DATE: + long savedTime = OVarIntSerializer.readAsLong(bytes) * MILLISEC_PER_DAY; + savedTime = convertDayToTimezone(TimeZone.getTimeZone("GMT"), ODateHelper.getDatabaseTimeZone(), savedTime); + value = new Date(savedTime); + break; + case EMBEDDED: + value = new ODocument(); + deserialize((ODocument) value, bytes); + if (((ODocument) value).containsField(ODocumentSerializable.CLASS_NAME)) { + String className = ((ODocument) value).field(ODocumentSerializable.CLASS_NAME); + try { + Class clazz = Class.forName(className); + ODocumentSerializable newValue = (ODocumentSerializable) clazz.newInstance(); + newValue.fromDocument((ODocument) value); + value = newValue; + } catch (Exception e) { + throw new RuntimeException(e); + } + } else + ODocumentInternal.addOwner((ODocument) value, document); + + break; + case EMBEDDEDSET: + value = readEmbeddedCollection(bytes, new OTrackedSet(document), document); + break; + case EMBEDDEDLIST: + value = readEmbeddedCollection(bytes, new OTrackedList(document), document); + break; + case LINKSET: + value = readLinkCollection(bytes, new ORecordLazySet(document)); + break; + case LINKLIST: + value = readLinkCollection(bytes, new ORecordLazyList(document)); + break; + case BINARY: + value = readBinary(bytes); + break; + case LINK: + value = readOptimizedLink(bytes); + break; + case LINKMAP: + value = readLinkMap(bytes, document); + break; + case EMBEDDEDMAP: + value = readEmbeddedMap(bytes, document); + break; + case DECIMAL: + value = ODecimalSerializer.INSTANCE.deserialize(bytes.bytes, bytes.offset); + bytes.skip(ODecimalSerializer.INSTANCE.getObjectSize(bytes.bytes, bytes.offset)); + break; + case LINKBAG: + ORidBag bag = new ORidBag(); + bag.fromStream(bytes); + bag.setOwner(document); + value = bag; + break; + case TRANSIENT: + break; + case CUSTOM: + try { + String className = readString(bytes); + Class clazz = Class.forName(className); + OSerializableStream stream = (OSerializableStream) clazz.newInstance(); + stream.fromStream(readBinary(bytes)); + if (stream instanceof OSerializableWrapper) + value = ((OSerializableWrapper) stream).getSerializable(); + else + value = stream; + } catch (Exception e) { + throw new RuntimeException(e); + } + break; + case ANY: + break; + + } + return value; + } + + private byte[] readBinary(BytesContainer bytes) { + int n = OVarIntSerializer.readAsInteger(bytes); + byte[] newValue = new byte[n]; + System.arraycopy(bytes.bytes, bytes.offset, newValue, 0, newValue.length); + bytes.skip(n); + return newValue; + } + + private Map readLinkMap(final BytesContainer bytes, final ODocument document) { + int size = OVarIntSerializer.readAsInteger(bytes); + Map result = new ORecordLazyMap(document); + while ((size--) > 0) { + OType keyType = readOType(bytes); + Object key = deserializeValue(bytes, keyType, document); + ORecordId value = readOptimizedLink(bytes); + if (value.equals(NULL_RECORD_ID)) + result.put(key, null); + else + result.put(key, value); + } + return result; + } + + private Object readEmbeddedMap(final BytesContainer bytes, final ODocument document) { + int size = OVarIntSerializer.readAsInteger(bytes); + final Map result = new OTrackedMap(document); + int last = 0; + while ((size--) > 0) { + OType keyType = readOType(bytes); + Object key = deserializeValue(bytes, keyType, document); + final int valuePos = readInteger(bytes); + final OType type = readOType(bytes); + if (valuePos != 0) { + int headerCursor = bytes.offset; + bytes.offset = valuePos; + Object value = deserializeValue(bytes, type, document); + if (bytes.offset > last) + last = bytes.offset; + bytes.offset = headerCursor; + result.put(key, value); + } else + result.put(key, null); + } + if (last > bytes.offset) + bytes.offset = last; + return result; + } + + private Collection readLinkCollection(BytesContainer bytes, Collection found) { + final int items = OVarIntSerializer.readAsInteger(bytes); + for (int i = 0; i < items; i++) { + ORecordId id = readOptimizedLink(bytes); + if (id.equals(NULL_RECORD_ID)) + found.add(null); + else + found.add(id); + } + return found; + } + + private ORecordId readOptimizedLink(final BytesContainer bytes) { + return new ORecordId(OVarIntSerializer.readAsInteger(bytes), OVarIntSerializer.readAsLong(bytes)); + } + + private Collection readEmbeddedCollection(final BytesContainer bytes, final Collection found, + final ODocument document) { + final int items = OVarIntSerializer.readAsInteger(bytes); + OType type = readOType(bytes); + + if (type == OType.ANY) { + for (int i = 0; i < items; i++) { + OType itemType = readOType(bytes); + if (itemType == OType.ANY) + found.add(null); + else + found.add(deserializeValue(bytes, itemType, document)); + } + return found; + } + // TODO: manage case where type is known + return null; + } + + private OType getLinkedType(ODocument document, OType type, String key) { + if (type != OType.EMBEDDEDLIST && type != OType.EMBEDDEDSET && type != OType.EMBEDDEDMAP) + return null; + OClass immutableClass = ODocumentInternal.getImmutableSchemaClass(document); + if (immutableClass != null) { + OProperty prop = immutableClass.getProperty(key); + if (prop != null) { + return prop.getLinkedType(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + public int serializeValue(final BytesContainer bytes, Object value, final OType type, final OType linkedType) { + int pointer = 0; + switch (type) { + case INTEGER: + case LONG: + case SHORT: + pointer = OVarIntSerializer.write(bytes, ((Number) value).longValue()); + break; + case STRING: + pointer = writeString(bytes, value.toString()); + break; + case DOUBLE: + long dg = Double.doubleToLongBits((Double) value); + pointer = bytes.alloc(OLongSerializer.LONG_SIZE); + OLongSerializer.INSTANCE.serializeLiteral(dg, bytes.bytes, pointer); + break; + case FLOAT: + int fg = Float.floatToIntBits((Float) value); + pointer = bytes.alloc(OIntegerSerializer.INT_SIZE); + OIntegerSerializer.INSTANCE.serializeLiteral(fg, bytes.bytes, pointer); + break; + case BYTE: + pointer = bytes.alloc(1); + bytes.bytes[pointer] = (Byte) value; + break; + case BOOLEAN: + pointer = bytes.alloc(1); + bytes.bytes[pointer] = ((Boolean) value) ? (byte) 1 : (byte) 0; + break; + case DATETIME: + if (value instanceof Long) { + pointer = OVarIntSerializer.write(bytes, (Long) value); + } else + pointer = OVarIntSerializer.write(bytes, ((Date) value).getTime()); + break; + case DATE: + long dateValue; + if (value instanceof Long) { + dateValue = (Long) value; + } else + dateValue = ((Date) value).getTime(); + dateValue = convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), dateValue); + pointer = OVarIntSerializer.write(bytes, dateValue / MILLISEC_PER_DAY); + break; + case EMBEDDED: + pointer = bytes.offset; + if (value instanceof ODocumentSerializable) { + ODocument cur = ((ODocumentSerializable) value).toDocument(); + cur.field(ODocumentSerializable.CLASS_NAME, value.getClass().getName()); + serialize(cur, bytes, false); + } else { + serialize((ODocument) value, bytes, false); + } + break; + case EMBEDDEDSET: + case EMBEDDEDLIST: + if (value.getClass().isArray()) + pointer = writeEmbeddedCollection(bytes, Arrays.asList(OMultiValue.array(value)), linkedType); + else + pointer = writeEmbeddedCollection(bytes, (Collection) value, linkedType); + break; + case DECIMAL: + BigDecimal decimalValue = (BigDecimal) value; + pointer = bytes.alloc(ODecimalSerializer.INSTANCE.getObjectSize(decimalValue)); + ODecimalSerializer.INSTANCE.serialize(decimalValue, bytes.bytes, pointer); + break; + case BINARY: + pointer = writeBinary(bytes, (byte[]) (value)); + break; + case LINKSET: + case LINKLIST: + Collection ridCollection = (Collection) value; + pointer = writeLinkCollection(bytes, ridCollection); + break; + case LINK: + if (!(value instanceof OIdentifiable)) + throw new OValidationException("Value '" + value + "' is not a OIdentifiable"); + + pointer = writeOptimizedLink(bytes, (OIdentifiable) value); + break; + case LINKMAP: + pointer = writeLinkMap(bytes, (Map) value); + break; + case EMBEDDEDMAP: + pointer = writeEmbeddedMap(bytes, (Map) value); + break; + case LINKBAG: + pointer = ((ORidBag) value).toStream(bytes); + break; + case CUSTOM: + if (!(value instanceof OSerializableStream)) + value = new OSerializableWrapper((Serializable) value); + pointer = writeString(bytes, value.getClass().getName()); + writeBinary(bytes, ((OSerializableStream) value).toStream()); + break; + case TRANSIENT: + break; + case ANY: + break; + } + return pointer; + } + + private int writeBinary(final BytesContainer bytes, final byte[] valueBytes) { + final int pointer = OVarIntSerializer.write(bytes, valueBytes.length); + final int start = bytes.alloc(valueBytes.length); + System.arraycopy(valueBytes, 0, bytes.bytes, start, valueBytes.length); + return pointer; + } + + private int writeLinkMap(final BytesContainer bytes, final Map map) { + final boolean disabledAutoConversion = + map instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) map).isAutoConvertToRecord(); + + if (disabledAutoConversion) + // AVOID TO FETCH RECORD + ((ORecordLazyMultiValue) map).setAutoConvertToRecord(false); + + try { + final int fullPos = OVarIntSerializer.write(bytes, map.size()); + for (Entry entry : map.entrySet()) { + // TODO:check skip of complex types + // FIXME: changed to support only string key on map + final OType type = OType.STRING; + writeOType(bytes, bytes.alloc(1), type); + writeString(bytes, entry.getKey().toString()); + if (entry.getValue() == null) + writeNullLink(bytes); + else + writeOptimizedLink(bytes, entry.getValue()); + } + return fullPos; + + } finally { + if (disabledAutoConversion) + ((ORecordLazyMultiValue) map).setAutoConvertToRecord(true); + } + } + + @SuppressWarnings("unchecked") + private int writeEmbeddedMap(BytesContainer bytes, Map map) { + final int[] pos = new int[map.size()]; + int i = 0; + Entry values[] = new Entry[map.size()]; + final int fullPos = OVarIntSerializer.write(bytes, map.size()); + for (Entry entry : map.entrySet()) { + // TODO:check skip of complex types + // FIXME: changed to support only string key on map + OType type = OType.STRING; + writeOType(bytes, bytes.alloc(1), type); + writeString(bytes, entry.getKey().toString()); + pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); + values[i] = entry; + i++; + } + + for (i = 0; i < values.length; i++) { + int pointer = 0; + final Object value = values[i].getValue(); + if (value != null) { + final OType type = getTypeFromValueEmbedded(value); + if (type == null) { + throw new OSerializationException( + "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer"); + } + pointer = serializeValue(bytes, value, type, null); + OIntegerSerializer.INSTANCE.serializeLiteral(pointer, bytes.bytes, pos[i]); + writeOType(bytes, (pos[i] + OIntegerSerializer.INT_SIZE), type); + } + } + return fullPos; + } + + private int writeNullLink(final BytesContainer bytes) { + final int pos = OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterId()); + OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterPosition()); + return pos; + + } + + private int writeOptimizedLink(final BytesContainer bytes, OIdentifiable link) { + if (!link.getIdentity().isPersistent()) { + final ORecord real = link.getRecord(); + if (real != null) + link = real; + } + final int pos = OVarIntSerializer.write(bytes, link.getIdentity().getClusterId()); + OVarIntSerializer.write(bytes, link.getIdentity().getClusterPosition()); + return pos; + } + + private int writeLinkCollection(final BytesContainer bytes, final Collection value) { + final int pos = OVarIntSerializer.write(bytes, value.size()); + + final boolean disabledAutoConversion = + value instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) value).isAutoConvertToRecord(); + + if (disabledAutoConversion) + // AVOID TO FETCH RECORD + ((ORecordLazyMultiValue) value).setAutoConvertToRecord(false); + + try { + for (OIdentifiable itemValue : value) { + // TODO: handle the null links + if (itemValue == null) + writeNullLink(bytes); + else + writeOptimizedLink(bytes, itemValue); + } + + } finally { + if (disabledAutoConversion) + ((ORecordLazyMultiValue) value).setAutoConvertToRecord(true); + } + + return pos; + } + + private int writeEmbeddedCollection(final BytesContainer bytes, final Collection value, final OType linkedType) { + final int pos = OVarIntSerializer.write(bytes, value.size()); + // TODO manage embedded type from schema and auto-determined. + writeOType(bytes, bytes.alloc(1), OType.ANY); + for (Object itemValue : value) { + // TODO:manage in a better way null entry + if (itemValue == null) { + writeOType(bytes, bytes.alloc(1), OType.ANY); + continue; + } + OType type; + if (linkedType == null) + type = getTypeFromValueEmbedded(itemValue); + else + type = linkedType; + if (type != null) { + writeOType(bytes, bytes.alloc(1), type); + serializeValue(bytes, itemValue, type, null); + } else { + throw new OSerializationException( + "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer"); + } + } + return pos; + } + + private OType getFieldType(final ODocumentEntry entry) { + OType type = entry.type; + if (type == null) { + final OProperty prop = entry.property; + if (prop != null) + type = prop.getType(); + + } + if (type == null || OType.ANY == type) + type = OType.getTypeByValue(entry.value); + return type; + } + + private OType getTypeFromValueEmbedded(final Object fieldValue) { + OType type = OType.getTypeByValue(fieldValue); + if (type == OType.LINK && fieldValue instanceof ODocument && !((ODocument) fieldValue).getIdentity().isValid()) + type = OType.EMBEDDED; + return type; + } + + protected String readString(final BytesContainer bytes) { + final int len = OVarIntSerializer.readAsInteger(bytes); + final String res = stringFromBytes(bytes.bytes, bytes.offset, len); + bytes.skip(len); + return res; + } + + protected int readInteger(final BytesContainer container) { + final int value = OIntegerSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset); + container.offset += OIntegerSerializer.INT_SIZE; + return value; + } + + private byte readByte(final BytesContainer container) { + return container.bytes[container.offset++]; + } + + private long readLong(final BytesContainer container) { + final long value = OLongSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset); + container.offset += OLongSerializer.LONG_SIZE; + return value; + } + + private int writeEmptyString(final BytesContainer bytes) { + return OVarIntSerializer.write(bytes, 0); + } + + private int writeString(final BytesContainer bytes, final String toWrite) { + final byte[] nameBytes = bytesFromString(toWrite); + final int pointer = OVarIntSerializer.write(bytes, nameBytes.length); + final int start = bytes.alloc(nameBytes.length); + System.arraycopy(nameBytes, 0, bytes.bytes, start, nameBytes.length); + return pointer; + } + + private byte[] bytesFromString(final String toWrite) { + try { + return toWrite.getBytes(CHARSET_UTF_8); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error on string encoding"), e); + } + } + + protected String stringFromBytes(final byte[] bytes, final int offset, final int len) { + try { + return new String(bytes, offset, len, CHARSET_UTF_8); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error on string decoding"), e); + } + } + + public OBinaryField deserializeField(final BytesContainer bytes, final OClass iClass, final String iFieldName) { + // TODO: check if integrate the binary disc binary comparator here + throw new UnsupportedOperationException("network serializer doesn't support comparators"); + } + + @Override + public OBinaryComparator getComparator() { + // TODO: check if integrate the binary disc binary comparator here + throw new UnsupportedOperationException("network serializer doesn't support comparators"); + } + + private long convertDayToTimezone(TimeZone from, TimeZone to, long time) { + Calendar fromCalendar = Calendar.getInstance(from); + fromCalendar.setTimeInMillis(time); + Calendar toCalendar = Calendar.getInstance(to); + toCalendar.setTimeInMillis(0); + toCalendar.set(Calendar.ERA, fromCalendar.get(Calendar.ERA)); + toCalendar.set(Calendar.YEAR, fromCalendar.get(Calendar.YEAR)); + toCalendar.set(Calendar.MONTH, fromCalendar.get(Calendar.MONTH)); + toCalendar.set(Calendar.DAY_OF_MONTH, fromCalendar.get(Calendar.DAY_OF_MONTH)); + toCalendar.set(Calendar.HOUR_OF_DAY, 0); + toCalendar.set(Calendar.MINUTE, 0); + toCalendar.set(Calendar.SECOND, 0); + toCalendar.set(Calendar.MILLISECOND, 0); + return toCalendar.getTimeInMillis(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OSerializableWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OSerializableWrapper.java new file mode 100755 index 00000000000..f3e3ac1433a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OSerializableWrapper.java @@ -0,0 +1,57 @@ +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.serialization.OSerializableStream; + +@SuppressWarnings("serial") +public class OSerializableWrapper implements OSerializableStream { + + private Serializable serializable; + + public OSerializableWrapper() { + } + + public OSerializableWrapper(Serializable ser) { + this.serializable = ser; + } + + @Override + public byte[] toStream() throws OSerializationException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + ObjectOutputStream writer = new ObjectOutputStream(output); + writer.writeObject(serializable); + writer.close(); + } catch (IOException e) { + throw OException.wrapException(new ODatabaseException("Error on serialization of Serializable"), e); + } + return output.toByteArray(); + } + + @Override + public OSerializableStream fromStream(byte[] iStream) throws OSerializationException { + ByteArrayInputStream stream = new ByteArrayInputStream(iStream); + try { + ObjectInputStream reader = new ObjectInputStream(stream); + serializable = (Serializable) reader.readObject(); + reader.close(); + } catch (Exception e) { + throw OException.wrapException(new ODatabaseException("Error on deserialization of Serializable"), e); + } + return this; + } + + public Serializable getSerializable() { + return serializable; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OVarIntSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OVarIntSerializer.java new file mode 100644 index 00000000000..f0563943608 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/binary/OVarIntSerializer.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.serialization.serializer.record.binary; + +public class OVarIntSerializer { + + public static int write(BytesContainer bytes, long value) { + value = signedToUnsigned(value); + int pos = bytes.offset; + writeUnsignedVarLong(value, bytes); + return pos; + + } + + public static short readAsShort(final BytesContainer bytes) { + return (short) readSignedVarLong(bytes); + } + + public static long readAsLong(final BytesContainer bytes) { + return readSignedVarLong(bytes); + } + + public static int readAsInteger(final BytesContainer bytes) { + return (int) readSignedVarLong(bytes); + } + + public static byte readAsByte(final BytesContainer bytes) { + return (byte) readSignedVarLong(bytes); + } + + /** + * Encodes a value using the variable-length encoding from Google Protocol Buffers. It uses zig-zag encoding to + * efficiently encode signed values. If values are known to be nonnegative, {@link #writeUnsignedVarLong(long, DataOutput)} should + * be used. + * + * @param value value to encode + * @param out to write bytes to + * + * @throws IOException if {@link DataOutput} throws {@link IOException} + */ + private static long signedToUnsigned(long value) { + return (value << 1) ^ (value >> 63); + } + + /** + * Encodes a value using the variable-length encoding from Google Protocol Buffers. Zig-zag is not used, so + * input must not be negative. If values can be negative, use {@link #writeSignedVarLong(long, DataOutput)} instead. This method + * treats negative input as like a large unsigned value. + * + * @param value value to encode + * @param out to write bytes to + * + * @return the number of bytes written + * + * @throws IOException if {@link DataOutput} throws {@link IOException} + */ + public static void writeUnsignedVarLong(long value, final BytesContainer bos) { + int pos; + while ((value & 0xFFFFFFFFFFFFFF80L) != 0L) { + // out.writeByte(((int) value & 0x7F) | 0x80); + pos = bos.alloc((short) 1); + bos.bytes[pos] = (byte) (value & 0x7F | 0x80); + value >>>= 7; + } + // out.writeByte((int) value & 0x7F); + pos = bos.alloc((short) 1); + bos.bytes[pos] = (byte) (value & 0x7F); + } + + /** + * @param in to read bytes from + * + * @return decode value + * + * @throws IOException if {@link DataInput} throws {@link IOException} + * @throws IllegalArgumentException if variable-length value does not terminate after 9 bytes have been read + * @see #writeSignedVarLong(long, DataOutput) + */ + public static long readSignedVarLong(final BytesContainer bytes) { + final long raw = readUnsignedVarLong(bytes); + // This undoes the trick in writeSignedVarLong() + final long temp = (((raw << 63) >> 63) ^ raw) >> 1; + // This extra step lets us deal with the largest signed values by + // treating + // negative results from read unsigned methods as like unsigned values + // Must re-flip the top bit if the original read value had it set. + return temp ^ (raw & (1L << 63)); + } + + public static long readUnsignedVarLong(final BytesContainer bytes) { + long value = 0L; + int i = 0; + long b; + while (((b = bytes.bytes[bytes.offset++]) & 0x80L) != 0) { + value |= (b & 0x7F) << i; + i += 7; + if (i > 63) + throw new IllegalArgumentException("Variable length quantity is too long (must be <= 63)"); + } + return value | (b << i); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerCSVAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerCSVAbstract.java new file mode 100755 index 00000000000..28052b53698 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerCSVAbstract.java @@ -0,0 +1,874 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record.string; + +import com.orientechnologies.common.collection.OLazyIterator; +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.annotation.OAfterSerialization; +import com.orientechnologies.orient.core.annotation.OBeforeSerialization; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.db.object.OLazyObjectMapInterface; +import com.orientechnologies.orient.core.db.record.OAutoConvertToRecord; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.db.record.ORecordElement.STATUS; +import com.orientechnologies.orient.core.db.record.ORecordLazyList; +import com.orientechnologies.orient.core.db.record.ORecordLazyMap; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.db.record.OTrackedList; +import com.orientechnologies.orient.core.db.record.OTrackedMap; +import com.orientechnologies.orient.core.db.record.OTrackedSet; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.entity.OEntityManagerInternal; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.object.OObjectSerializerHelperManager; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringBuilderSerializable; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringSerializerEmbedded; +import com.orientechnologies.orient.core.storage.OStorageProxy; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +@SuppressWarnings({ "unchecked", "serial" }) +public abstract class ORecordSerializerCSVAbstract extends ORecordSerializerStringAbstract { + public static final char FIELD_VALUE_SEPARATOR = ':'; + private final boolean preferSBTreeRIDSet = OGlobalConfiguration.PREFER_SBTREE_SET.getValueAsBoolean(); + + /** + * Serialize the link. + * + * @param buffer + * @param iParentRecord + * @param iLinked + * Can be an instance of ORID or a Record + * @return + */ + private static OIdentifiable linkToStream(final StringBuilder buffer, final ODocument iParentRecord, Object iLinked) { + if (iLinked == null) + // NULL REFERENCE + return null; + + OIdentifiable resultRid = null; + ORID rid; + + if (iLinked instanceof ORID) { + // JUST THE REFERENCE + rid = (ORID) iLinked; + + assert rid.getIdentity().isValid() || (ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() instanceof OStorageProxy) : + "Impossible to serialize invalid link " + rid.getIdentity(); + resultRid = rid; + } else { + if (iLinked instanceof String) + iLinked = new ORecordId((String) iLinked); + else if (!(iLinked instanceof ORecord)) { + // NOT RECORD: TRY TO EXTRACT THE DOCUMENT IF ANY + final String boundDocumentField = OObjectSerializerHelperManager.getInstance().getDocumentBoundField(iLinked.getClass()); + if (boundDocumentField != null) + iLinked = OObjectSerializerHelperManager.getInstance().getFieldValue(iLinked, boundDocumentField); + } + + if (!(iLinked instanceof OIdentifiable)) + throw new IllegalArgumentException("Invalid object received. Expected a OIdentifiable but received type=" + + iLinked.getClass().getName() + " and value=" + iLinked); + + // RECORD + ORecord iLinkedRecord = ((OIdentifiable) iLinked).getRecord(); + rid = iLinkedRecord.getIdentity(); + + assert rid.getIdentity().isValid() || (ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() instanceof OStorageProxy) : + "Impossible to serialize invalid link " + rid.getIdentity(); + + final ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.get(); + if (iParentRecord != null) { + if (!database.isRetainRecords()) + // REPLACE CURRENT RECORD WITH ITS ID: THIS SAVES A LOT OF MEMORY + resultRid = iLinkedRecord.getIdentity(); + } + } + + if (rid.isValid()) + rid.toString(buffer); + + return resultRid; + } + + public Object fieldFromStream(final ORecord iSourceRecord, final OType iType, OClass iLinkedClass, OType iLinkedType, + final String iName, final String iValue) { + + if (iValue == null) + return null; + + switch (iType) { + case EMBEDDEDLIST: + case EMBEDDEDSET: + return embeddedCollectionFromStream((ODocument) iSourceRecord, iType, iLinkedClass, iLinkedType, iValue); + + case LINKSET: + case LINKLIST: { + if (iValue.length() == 0) + return null; + + // REMOVE BEGIN & END COLLECTIONS CHARACTERS IF IT'S A COLLECTION + final String value = iValue.startsWith("[") || iValue.startsWith("<") ? iValue.substring(1, iValue.length() - 1) : iValue; + + if (iType == OType.LINKLIST) { + return new ORecordLazyList((ODocument) iSourceRecord).setStreamedContent(new StringBuilder(value)); + } else { + return unserializeSet((ODocument) iSourceRecord, value); + } + } + + case LINKMAP: { + if (iValue.length() == 0) + return null; + + // REMOVE BEGIN & END MAP CHARACTERS + String value = iValue.substring(1, iValue.length() - 1); + + @SuppressWarnings("rawtypes") + final Map map = new ORecordLazyMap((ODocument) iSourceRecord, ODocument.RECORD_TYPE); + + if (value.length() == 0) + return map; + + final List items = OStringSerializerHelper.smartSplit(value, OStringSerializerHelper.RECORD_SEPARATOR, true, false); + + // EMBEDDED LITERALS + for (String item : items) { + if (item != null && !item.isEmpty()) { + final List entry = OStringSerializerHelper.smartSplit(item, OStringSerializerHelper.ENTRY_SEPARATOR); + if (!entry.isEmpty()) { + String mapValue = entry.get(1); + if (mapValue != null && !mapValue.isEmpty()) + mapValue = mapValue.substring(1); + map.put(fieldTypeFromStream((ODocument) iSourceRecord, OType.STRING, entry.get(0)), new ORecordId(mapValue)); + } + + } + } + return map; + } + + case EMBEDDEDMAP: + return embeddedMapFromStream((ODocument) iSourceRecord, iLinkedType, iValue, iName); + + case LINK: + if (iValue.length() > 1) { + int pos = iValue.indexOf(OStringSerializerHelper.CLASS_SEPARATOR); + if (pos > -1) + ((OMetadataInternal) ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata()).getImmutableSchemaSnapshot() + .getClass(iValue.substring(1, pos)); + else + pos = 0; + + final String linkAsString = iValue.substring(pos + 1); + try { + return new ORecordId(linkAsString); + } catch (IllegalArgumentException e) { + OLogManager.instance().error(this, "Error on unmarshalling field '%s' of record '%s': value '%s' is not a link", iName, + iSourceRecord, linkAsString); + return new ORecordId(); + } + } else + return null; + + case EMBEDDED: + if (iValue.length() > 2) { + // REMOVE BEGIN & END EMBEDDED CHARACTERS + final String value = iValue.substring(1, iValue.length() - 1); + + final Object embeddedObject = OStringSerializerEmbedded.INSTANCE.fromStream(value); + if (embeddedObject instanceof ODocument) + ODocumentInternal.addOwner((ODocument) embeddedObject, iSourceRecord); + + // RECORD + return embeddedObject; + } else + return null; + case LINKBAG: + final String value = iValue.charAt(0) == OStringSerializerHelper.BAG_BEGIN ? iValue.substring(1, iValue.length() - 1) + : iValue; + return ORidBag.fromStream(value); + default: + return fieldTypeFromStream((ODocument) iSourceRecord, iType, iValue); + } + } + + public Map embeddedMapFromStream(final ODocument iSourceDocument, final OType iLinkedType, final String iValue, + final String iName) { + if (iValue.length() == 0) + return null; + + // REMOVE BEGIN & END MAP CHARACTERS + String value = iValue.substring(1, iValue.length() - 1); + + @SuppressWarnings("rawtypes") + Map map; + if (iLinkedType == OType.LINK || iLinkedType == OType.EMBEDDED) + map = new ORecordLazyMap(iSourceDocument, ODocument.RECORD_TYPE); + else + map = new OTrackedMap(iSourceDocument); + + if (value.length() == 0) + return map; + + final List items = OStringSerializerHelper.smartSplit(value, OStringSerializerHelper.RECORD_SEPARATOR, true, false); + + // EMBEDDED LITERALS + + if (map instanceof ORecordElement) + ((ORecordElement) map).setInternalStatus(STATUS.UNMARSHALLING); + + for (String item : items) { + if (item != null && !item.isEmpty()) { + final List entries = OStringSerializerHelper.smartSplit(item, OStringSerializerHelper.ENTRY_SEPARATOR, true, false); + if (!entries.isEmpty()) { + final Object mapValueObject; + if (entries.size() > 1) { + String mapValue = entries.get(1); + + final OType linkedType; + + if (iLinkedType == null) + if (!mapValue.isEmpty()) { + linkedType = getType(mapValue); + if ((iName == null || iSourceDocument.fieldType(iName) == null + || iSourceDocument.fieldType(iName) != OType.EMBEDDEDMAP) && isConvertToLinkedMap(map, linkedType)) { + // CONVERT IT TO A LAZY MAP + map = new ORecordLazyMap(iSourceDocument, ODocument.RECORD_TYPE); + ((ORecordElement) map).setInternalStatus(STATUS.UNMARSHALLING); + } else if (map instanceof ORecordLazyMap && linkedType != OType.LINK) { + map = new OTrackedMap(iSourceDocument, map, null); + } + } else + linkedType = OType.EMBEDDED; + else + linkedType = iLinkedType; + + if (linkedType == OType.EMBEDDED && mapValue.length() >= 2) + mapValue = mapValue.substring(1, mapValue.length() - 1); + + mapValueObject = fieldTypeFromStream(iSourceDocument, linkedType, mapValue); + + if (mapValueObject != null && mapValueObject instanceof ODocument) + ODocumentInternal.addOwner((ODocument) mapValueObject, iSourceDocument); + } else + mapValueObject = null; + + final Object key = fieldTypeFromStream(iSourceDocument, OType.STRING, entries.get(0)); + try { + map.put(key, mapValueObject); + } catch (ClassCastException e) { + throw OException.wrapException(new OSerializationException( + "Cannot load map because the type was not the expected: key=" + key + "(type " + key.getClass().toString() + + "), value=" + mapValueObject + "(type " + key.getClass() + ")"), e); + } + } + + } + } + + if (map instanceof ORecordElement) + ((ORecordElement) map).setInternalStatus(STATUS.LOADED); + + return map; + } + + public void fieldToStream(final ODocument iRecord, final StringBuilder iOutput, OUserObject2RecordHandler iObjHandler, + final OType iType, final OClass iLinkedClass, final OType iLinkedType, final String iName, final Object iValue, + final boolean iSaveOnlyDirty) { + if (iValue == null) + return; + + final long timer = PROFILER.startChrono(); + + switch (iType) { + + case LINK: { + if (!(iValue instanceof OIdentifiable)) + throw new OSerializationException( + "Found an unexpected type during marshalling of a LINK where a OIdentifiable (ORID or any Record) was expected. The string representation of the object is: " + + iValue); + + if (!((OIdentifiable) iValue).getIdentity().isValid() && iValue instanceof ODocument && ((ODocument) iValue).isEmbedded()) { + // WRONG: IT'S EMBEDDED! + fieldToStream(iRecord, iOutput, iObjHandler, OType.EMBEDDED, iLinkedClass, iLinkedType, iName, iValue, iSaveOnlyDirty); + } else { + final Object link = linkToStream(iOutput, iRecord, iValue); + if (link != null) + // OVERWRITE CONTENT + iRecord.field(iName, link); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.link2string"), "Serialize link to string", timer); + } + break; + } + + case LINKLIST: { + iOutput.append(OStringSerializerHelper.LIST_BEGIN); + + if (iValue instanceof ORecordLazyList && ((ORecordLazyList) iValue).getStreamedContent() != null) { + iOutput.append(((ORecordLazyList) iValue).getStreamedContent()); + PROFILER.updateCounter(PROFILER.getProcessMetric("serializer.record.string.linkList2string.cached"), + "Serialize linklist to string in stream mode", +1); + } else { + final ORecordLazyList coll; + final Iterator it; + if (iValue instanceof OMultiCollectionIterator) { + final OMultiCollectionIterator iterator = (OMultiCollectionIterator) iValue; + iterator.reset(); + it = iterator; + coll = null; + } else if (!(iValue instanceof ORecordLazyList)) { + // FIRST TIME: CONVERT THE ENTIRE COLLECTION + coll = new ORecordLazyList(iRecord); + + if (iValue.getClass().isArray()) { + Iterable iterab = OMultiValue.getMultiValueIterable(iValue, false); + for (Object i : iterab) { + coll.add((OIdentifiable) i); + } + } else { + coll.addAll((Collection) iValue); + ((Collection) iValue).clear(); + } + + iRecord.field(iName, coll); + it = coll.rawIterator(); + } else { + // LAZY LIST + coll = (ORecordLazyList) iValue; + if (coll.getStreamedContent() != null) { + // APPEND STREAMED CONTENT + iOutput.append(coll.getStreamedContent()); + PROFILER.updateCounter(PROFILER.getProcessMetric("serializer.record.string.linkList2string.cached"), + "Serialize linklist to string in stream mode", +1); + it = coll.newItemsIterator(); + } else + it = coll.rawIterator(); + } + + if (it != null && it.hasNext()) { + final StringBuilder buffer = new StringBuilder(128); + for (int items = 0; it.hasNext(); items++) { + if (items > 0) + buffer.append(OStringSerializerHelper.RECORD_SEPARATOR); + + final OIdentifiable item = it.next(); + + final OIdentifiable newRid = linkToStream(buffer, iRecord, item); + if (newRid != null) + ((OLazyIterator) it).update(newRid); + } + + if (coll != null) + coll.convertRecords2Links(); + + iOutput.append(buffer); + + // UPDATE THE STREAM + if (coll != null) + coll.setStreamedContent(buffer); + } + } + + iOutput.append(OStringSerializerHelper.LIST_END); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.linkList2string"), "Serialize linklist to string", + timer); + break; + } + + case LINKSET: { + if (!(iValue instanceof OStringBuilderSerializable)) { + if (iValue instanceof OAutoConvertToRecord) + ((OAutoConvertToRecord) iValue).setAutoConvertToRecord(false); + + final Collection coll; + + // FIRST TIME: CONVERT THE ENTIRE COLLECTION + if (!(iValue instanceof ORecordLazySet)) { + final ORecordLazySet set = new ORecordLazySet(iRecord); + set.addAll((Collection) iValue); + iRecord.field(iName, set); + coll = set; + } else + coll = (Collection) iValue; + + serializeSet(coll, iOutput); + + } else { + // LAZY SET + final OStringBuilderSerializable coll = (OStringBuilderSerializable) iValue; + coll.toStream(iOutput); + } + + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.linkSet2string"), "Serialize linkset to string", + timer); + break; + } + + case LINKMAP: { + iOutput.append(OStringSerializerHelper.MAP_BEGIN); + + Map map = (Map) iValue; + + // LINKED MAP + if (map instanceof OLazyObjectMapInterface) + ((OLazyObjectMapInterface) map).setConvertToRecord(false); + + boolean invalidMap = false; + try { + int items = 0; + for (Map.Entry entry : map.entrySet()) { + if (items++ > 0) + iOutput.append(OStringSerializerHelper.RECORD_SEPARATOR); + + fieldTypeToString(iOutput, OType.STRING, entry.getKey()); + iOutput.append(OStringSerializerHelper.ENTRY_SEPARATOR); + final Object link = linkToStream(iOutput, iRecord, entry.getValue()); + + if (link != null && !invalidMap) + // IDENTITY IS CHANGED, RE-SET INTO THE COLLECTION TO RECOMPUTE THE HASH + invalidMap = true; + } + } finally { + if (map instanceof OLazyObjectMapInterface) { + ((OLazyObjectMapInterface) map).setConvertToRecord(true); + } + } + + if (invalidMap) { + final ORecordLazyMap newMap = new ORecordLazyMap(iRecord, ODocument.RECORD_TYPE); + + // REPLACE ALL CHANGED ITEMS + for (Map.Entry entry : map.entrySet()) { + newMap.put(entry.getKey(), (OIdentifiable) entry.getValue()); + } + map.clear(); + iRecord.field(iName, newMap); + } + + iOutput.append(OStringSerializerHelper.MAP_END); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.linkMap2string"), "Serialize linkmap to string", + timer); + break; + } + + case EMBEDDED: + if (iValue instanceof ORecord) { + iOutput.append(OStringSerializerHelper.EMBEDDED_BEGIN); + toString((ORecord) iValue, iOutput, null, iObjHandler, false, true); + iOutput.append(OStringSerializerHelper.EMBEDDED_END); + } else if (iValue instanceof ODocumentSerializable) { + final ODocument doc = ((ODocumentSerializable) iValue).toDocument(); + doc.field(ODocumentSerializable.CLASS_NAME, iValue.getClass().getName()); + + iOutput.append(OStringSerializerHelper.EMBEDDED_BEGIN); + toString(doc, iOutput, null, iObjHandler, false, true); + iOutput.append(OStringSerializerHelper.EMBEDDED_END); + + } else if (iValue != null) + iOutput.append(iValue.toString()); + PROFILER + .stopChrono(PROFILER.getProcessMetric("serializer.record.string.embed2string"), "Serialize embedded to string", timer); + break; + + case EMBEDDEDLIST: + embeddedCollectionToStream(null, iObjHandler, iOutput, iLinkedClass, iLinkedType, iValue, iSaveOnlyDirty, + false); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedList2string"), + "Serialize embeddedlist to string", timer); + break; + + case EMBEDDEDSET: + embeddedCollectionToStream(null, iObjHandler, iOutput, iLinkedClass, iLinkedType, iValue, iSaveOnlyDirty, + true); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedSet2string"), "Serialize embeddedset to string", + timer); + break; + + case EMBEDDEDMAP: { + embeddedMapToStream(null, iObjHandler, iOutput, iLinkedClass, iLinkedType, iValue, iSaveOnlyDirty); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedMap2string"), "Serialize embeddedmap to string", + timer); + break; + } + + case LINKBAG: { + iOutput.append(OStringSerializerHelper.BAG_BEGIN); + ((ORidBag) iValue).toStream(iOutput); + iOutput.append(OStringSerializerHelper.BAG_END); + break; + } + + default: + fieldTypeToString(iOutput, iType, iValue); + } + } + + public void embeddedMapToStream(ODatabase iDatabase, final OUserObject2RecordHandler iObjHandler, final StringBuilder iOutput, + final OClass iLinkedClass, OType iLinkedType, final Object iValue, final boolean iSaveOnlyDirty) { + iOutput.append(OStringSerializerHelper.MAP_BEGIN); + + if (iValue != null) { + int items = 0; + // EMBEDDED OBJECTS + for (Entry o : ((Map) iValue).entrySet()) { + if (items > 0) + iOutput.append(OStringSerializerHelper.RECORD_SEPARATOR); + + if (o != null) { + fieldTypeToString(iOutput, OType.STRING, o.getKey()); + iOutput.append(OStringSerializerHelper.ENTRY_SEPARATOR); + + if (o.getValue() instanceof ODocument && ((ODocument) o.getValue()).getIdentity().isValid()) { + fieldTypeToString(iOutput, OType.LINK, o.getValue()); + } else if (o.getValue() instanceof ORecord || o.getValue() instanceof ODocumentSerializable) { + final ODocument record; + if (o.getValue() instanceof ODocument) + record = (ODocument) o.getValue(); + else if (o.getValue() instanceof ODocumentSerializable) { + record = ((ODocumentSerializable) o.getValue()).toDocument(); + record.field(ODocumentSerializable.CLASS_NAME, o.getValue().getClass().getName()); + } else { + if (iDatabase == null && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) + iDatabase = ODatabaseRecordThreadLocal.INSTANCE.get(); + + record = OObjectSerializerHelperManager.getInstance().toStream(o.getValue(), + new ODocument(o.getValue().getClass().getSimpleName()), iDatabase instanceof ODatabaseObject ? + ((ODatabaseObject) iDatabase).getEntityManager() : + OEntityManagerInternal.INSTANCE, iLinkedClass, + iObjHandler != null ? iObjHandler : new OUserObject2RecordHandler() { + + public Object getUserObjectByRecord(OIdentifiable iRecord, final String iFetchPlan) { + return iRecord; + } + + public ORecord getRecordByUserObject(Object iPojo, boolean iCreateIfNotAvailable) { + return new ODocument(iLinkedClass); + } + + public boolean existsUserObjectByRID(ORID iRID) { + return false; + } + + public void registerUserObject(Object iObject, ORecord iRecord) { + } + + public void registerUserObjectAfterLinkSave(ORecord iRecord) { + } + }, null, iSaveOnlyDirty); + } + iOutput.append(OStringSerializerHelper.EMBEDDED_BEGIN); + toString(record, iOutput, null, iObjHandler, false, true); + iOutput.append(OStringSerializerHelper.EMBEDDED_END); + } else if (o.getValue() instanceof Set) { + // SUB SET + fieldTypeToString(iOutput, OType.EMBEDDEDSET, o.getValue()); + } else if (o.getValue() instanceof Collection) { + // SUB LIST + fieldTypeToString(iOutput, OType.EMBEDDEDLIST, o.getValue()); + } else if (o.getValue() instanceof Map) { + // SUB MAP + fieldTypeToString(iOutput, OType.EMBEDDEDMAP, o.getValue()); + } else { + // EMBEDDED LITERALS + if (iLinkedType == null && o.getValue() != null) { + fieldTypeToString(iOutput, OType.getTypeByClass(o.getValue().getClass()), o.getValue()); + } else { + fieldTypeToString(iOutput, iLinkedType, o.getValue()); + } + } + } + items++; + } + } + + iOutput.append(OStringSerializerHelper.MAP_END); + } + + public Object embeddedCollectionFromStream(final ODocument iDocument, final OType iType, OClass iLinkedClass, + final OType iLinkedType, final String iValue) { + if (iValue.length() == 0) + return null; + + // REMOVE BEGIN & END COLLECTIONS CHARACTERS IF IT'S A COLLECTION + final String value = iValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN + || iValue.charAt(0) == OStringSerializerHelper.SET_BEGIN ? iValue.substring(1, iValue.length() - 1) : iValue; + + Collection coll; + if (iLinkedType == OType.LINK) { + if (iDocument != null) + coll = (Collection) (iType == OType.EMBEDDEDLIST ? + new ORecordLazyList(iDocument).setStreamedContent(new StringBuilder(value)) : + unserializeSet(iDocument, value)); + else { + if (iType == OType.EMBEDDEDLIST) + coll = (Collection) new ORecordLazyList().setStreamedContent(new StringBuilder(value)); + else { + return unserializeSet(iDocument, value); + } + } + } else + coll = iType == OType.EMBEDDEDLIST ? new OTrackedList(iDocument) : new OTrackedSet(iDocument); + + if (value.length() == 0) + return coll; + + OType linkedType; + + if (coll instanceof ORecordElement) + ((ORecordElement) coll).setInternalStatus(STATUS.UNMARSHALLING); + + final List items = OStringSerializerHelper.smartSplit(value, OStringSerializerHelper.RECORD_SEPARATOR, true, false); + for (String item : items) { + Object objectToAdd = null; + linkedType = null; + + if (item.equals("null")) + // NULL VALUE + objectToAdd = null; + else if (item.length() > 2 && item.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN) { + // REMOVE EMBEDDED BEGIN/END CHARS + item = item.substring(1, item.length() - 1); + + if (!item.isEmpty()) { + // EMBEDDED RECORD, EXTRACT THE CLASS NAME IF DIFFERENT BY THE PASSED (SUB-CLASS OR IT WAS PASSED NULL) + iLinkedClass = OStringSerializerHelper.getRecordClassName(item, iLinkedClass); + + if (iLinkedClass != null) { + ODocument doc = new ODocument(); + objectToAdd = fromString(item, doc, null); + ODocumentInternal.fillClassNameIfNeeded(doc, iLinkedClass.getName()); + } else + // EMBEDDED OBJECT + objectToAdd = fieldTypeFromStream(iDocument, OType.EMBEDDED, item); + } + } else { + if (linkedType == null) { + final char begin = item.length() > 0 ? item.charAt(0) : OStringSerializerHelper.LINK; + + // AUTO-DETERMINE LINKED TYPE + if (begin == OStringSerializerHelper.LINK) + linkedType = OType.LINK; + else + linkedType = getType(item); + + if (linkedType == null) + throw new IllegalArgumentException( + "Linked type cannot be null. Probably the serialized type has not stored the type along with data"); + } + + if (iLinkedType == OType.CUSTOM) + item = item.substring(1, item.length() - 1); + + objectToAdd = fieldTypeFromStream(iDocument, linkedType, item); + } + + if (objectToAdd != null && objectToAdd instanceof ODocument && coll instanceof ORecordElement) + ODocumentInternal.addOwner((ODocument) objectToAdd, (ORecordElement) coll); + + ((Collection) coll).add(objectToAdd); + } + + if (coll instanceof ORecordElement) + ((ORecordElement) coll).setInternalStatus(STATUS.LOADED); + + return coll; + } + + public StringBuilder embeddedCollectionToStream(ODatabase iDatabase, final OUserObject2RecordHandler iObjHandler, + final StringBuilder iOutput, final OClass iLinkedClass, final OType iLinkedType, final Object iValue, + final boolean iSaveOnlyDirty, final boolean iSet) { + iOutput.append(iSet ? OStringSerializerHelper.SET_BEGIN : OStringSerializerHelper.LIST_BEGIN); + + final Iterator iterator = OMultiValue.getMultiValueIterator(iValue, false); + + OType linkedType = iLinkedType; + + for (int i = 0; iterator.hasNext(); ++i) { + final Object o = iterator.next(); + + if (i > 0) + iOutput.append(OStringSerializerHelper.RECORD_SEPARATOR); + + if (o == null) { + iOutput.append("null"); + continue; + } + + OIdentifiable id = null; + ODocument doc = null; + + final OClass linkedClass; + if (!(o instanceof OIdentifiable)) { + final String fieldBound = OObjectSerializerHelperManager.getInstance().getDocumentBoundField(o.getClass()); + if (fieldBound != null) { + OObjectSerializerHelperManager.getInstance().invokeCallback(o, null, OBeforeSerialization.class); + doc = (ODocument) OObjectSerializerHelperManager.getInstance().getFieldValue(o, fieldBound); + OObjectSerializerHelperManager.getInstance().invokeCallback(o, doc, OAfterSerialization.class); + id = doc; + } else if (iLinkedType == null) + linkedType = OType.getTypeByClass(o.getClass()); + + linkedClass = iLinkedClass; + } else { + id = (OIdentifiable) o; + + if (iLinkedType == null) + // AUTO-DETERMINE LINKED TYPE + if (id.getIdentity().isValid()) + linkedType = OType.LINK; + else + linkedType = OType.EMBEDDED; + + if (id instanceof ODocument) { + doc = (ODocument) id; + + if (doc.hasOwners()) + linkedType = OType.EMBEDDED; + + assert linkedType == OType.EMBEDDED || id.getIdentity().isValid() || (ODatabaseRecordThreadLocal.INSTANCE.get() + .getStorage() instanceof OStorageProxy) : "Impossible to serialize invalid link " + id.getIdentity(); + + linkedClass = ODocumentInternal.getImmutableSchemaClass(doc); + } else + linkedClass = null; + } + + if (id != null && linkedType != OType.LINK) + iOutput.append(OStringSerializerHelper.EMBEDDED_BEGIN); + + if (linkedType == OType.EMBEDDED && o instanceof OIdentifiable) + toString((ORecord) ((OIdentifiable) o).getRecord(), iOutput, null); + else if (linkedType != OType.LINK && (linkedClass != null || doc != null)) { + if (id == null) { + // EMBEDDED OBJECTS + if (iDatabase == null && ODatabaseRecordThreadLocal.INSTANCE.isDefined()) + iDatabase = ODatabaseRecordThreadLocal.INSTANCE.get(); + + id = OObjectSerializerHelperManager.getInstance().toStream(o, new ODocument(o.getClass().getSimpleName()), + iDatabase instanceof ODatabaseObject ? + ((ODatabaseObject) iDatabase).getEntityManager() : + OEntityManagerInternal.INSTANCE, iLinkedClass, + iObjHandler != null ? iObjHandler : new OUserObject2RecordHandler() { + public Object getUserObjectByRecord(OIdentifiable iRecord, final String iFetchPlan) { + return iRecord; + } + + public ORecord getRecordByUserObject(Object iPojo, boolean iCreateIfNotAvailable) { + return new ODocument(linkedClass); + } + + public boolean existsUserObjectByRID(ORID iRID) { + return false; + } + + public void registerUserObject(Object iObject, ORecord iRecord) { + } + + public void registerUserObjectAfterLinkSave(ORecord iRecord) { + } + }, null, iSaveOnlyDirty); + } + toString(doc, iOutput, null, iObjHandler, false, true); + } else { + // EMBEDDED LITERALS + if (iLinkedType == null) { + if (o != null) + linkedType = OType.getTypeByClass(o.getClass()); + } else if (iLinkedType == OType.CUSTOM) + iOutput.append(OStringSerializerHelper.CUSTOM_TYPE); + fieldTypeToString(iOutput, linkedType, o); + } + + if (id != null && linkedType != OType.LINK) + iOutput.append(OStringSerializerHelper.EMBEDDED_END); + } + + iOutput.append(iSet ? OStringSerializerHelper.SET_END : OStringSerializerHelper.LIST_END); + return iOutput; + } + + protected boolean isConvertToLinkedMap(Map map, final OType linkedType) { + boolean convert = (linkedType == OType.LINK && !(map instanceof ORecordLazyMap)); + if (convert) { + for (Object value : map.values()) + if (!(value instanceof OIdentifiable)) + return false; + } + return convert; + } + + private void serializeSet(final Collection coll, final StringBuilder iOutput) { + iOutput.append(OStringSerializerHelper.SET_BEGIN); + int i = 0; + for (OIdentifiable rid : coll) { + if (i++ > 0) + iOutput.append(','); + + iOutput.append(rid.getIdentity().toString()); + } + iOutput.append(OStringSerializerHelper.SET_END); + } + + private ORecordLazySet unserializeSet(final ODocument iSourceRecord, final String value) { + final ORecordLazySet coll = new ORecordLazySet(iSourceRecord); + final List items = OStringSerializerHelper.smartSplit(value, OStringSerializerHelper.RECORD_SEPARATOR); + for (String item : items) { + if (item.length() == 0) + coll.add(new ORecordId()); + else { + if (item.startsWith("#")) + coll.add(new ORecordId(item)); + else { + final ORecord doc = fromString(item); + if (doc instanceof ODocument) + ODocumentInternal.addOwner((ODocument) doc, iSourceRecord); + + coll.add(doc); + } + } + } + return coll; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerJSON.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerJSON.java new file mode 100755 index 00000000000..f71204b26d4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerJSON.java @@ -0,0 +1,776 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record.string; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OStringParser; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyList; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.db.record.OTrackedList; +import com.orientechnologies.orient.core.db.record.OTrackedSet; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.fetch.OFetchHelper; +import com.orientechnologies.orient.core.fetch.OFetchPlan; +import com.orientechnologies.orient.core.fetch.json.OJSONFetchContext; +import com.orientechnologies.orient.core.fetch.json.OJSONFetchListener; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.ORecordStringable; +import com.orientechnologies.orient.core.record.impl.*; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.OJSONWriter; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.StringWriter; +import java.text.ParseException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings("serial") +public class ORecordSerializerJSON extends ORecordSerializerStringAbstract { + + public static final String NAME = "json"; + public static final ORecordSerializerJSON INSTANCE = new ORecordSerializerJSON(); + public static final String ATTRIBUTE_FIELD_TYPES = "@fieldTypes"; + public static final char[] PARAMETER_SEPARATOR = new char[] { ':', ',' }; + public static final int INITIAL_SIZE = 5000; + private static final Long MAX_INT = (long) Integer.MAX_VALUE; + private static final Long MIN_INT = (long) Integer.MIN_VALUE; + private static final Double MAX_FLOAT = (double) Float.MAX_VALUE; + private static final Double MIN_FLOAT = (double) Float.MIN_VALUE; + + private interface CollectionItemVisitor { + void visitItem(Object item); + } + + public static class FormatSettings { + public boolean includeVer; + public boolean includeType; + public boolean includeId; + public boolean includeClazz; + public boolean attribSameRow; + public boolean alwaysFetchEmbeddedDocuments; + public int indentLevel; + public String fetchPlan = null; + public boolean keepTypes = true; + public boolean dateAsLong = false; + public boolean prettyPrint = false; + + public FormatSettings(final String iFormat) { + if (iFormat == null) { + includeType = true; + includeVer = true; + includeId = true; + includeClazz = true; + attribSameRow = true; + indentLevel = 0; + fetchPlan = ""; + keepTypes = true; + alwaysFetchEmbeddedDocuments = true; + } else { + includeType = false; + includeVer = false; + includeId = false; + includeClazz = false; + attribSameRow = false; + alwaysFetchEmbeddedDocuments = false; + indentLevel = 0; + keepTypes = false; + + if (iFormat != null && !iFormat.isEmpty()) { + final String[] format = iFormat.split(","); + for (String f : format) + if (f.equals("type")) + includeType = true; + else if (f.equals("rid")) + includeId = true; + else if (f.equals("version")) + includeVer = true; + else if (f.equals("class")) + includeClazz = true; + else if (f.equals("attribSameRow")) + attribSameRow = true; + else if (f.startsWith("indent")) + indentLevel = Integer.parseInt(f.substring(f.indexOf(':') + 1)); + else if (f.startsWith("fetchPlan")) + fetchPlan = f.substring(f.indexOf(':') + 1); + else if (f.startsWith("keepTypes")) + keepTypes = true; + else if (f.startsWith("alwaysFetchEmbedded")) + alwaysFetchEmbeddedDocuments = true; + else if (f.startsWith("dateAsLong")) + dateAsLong = true; + else if (f.startsWith("prettyPrint")) + prettyPrint = true; + else if (f.startsWith("graph") || f.startsWith("shallow")) + // SUPPORTED IN OTHER PARTS + ; + else + throw new IllegalArgumentException("Unrecognized JSON formatting option: " + f); + } + } + } + } + + @Override + public int getCurrentVersion() { + return 0; + } + + @Override + public int getMinSupportedVersion() { + return 0; + } + + public ORecord fromString(String iSource, ORecord iRecord, final String[] iFields, boolean needReload) { + return fromString(iSource, iRecord, iFields, null, needReload); + } + + @Override + public ORecord fromString(String iSource, ORecord iRecord, final String[] iFields) { + return fromString(iSource, iRecord, iFields, null, false); + } + + public ORecord fromString(String iSource, ORecord iRecord, final String[] iFields, final String iOptions, boolean needReload) { + iSource = unwrapSource(iSource); + + String className = null; + boolean noMap = false; + if (iOptions != null) { + final String[] format = iOptions.split(","); + for (String f : format) + if (f.equalsIgnoreCase("noMap")) + noMap = true; + } + + if (iRecord != null) + // RESET ALL THE FIELDS + iRecord.clear(); + + final List fields = OStringSerializerHelper.smartSplit(iSource, PARAMETER_SEPARATOR, 0, -1, true, true, false, false, + ' ', '\n', '\r', '\t'); + + if (fields.size() % 2 != 0) + throw new OSerializationException( + "Error on unmarshalling JSON content: wrong format \"" + iSource + "\". Use : "); + + Map fieldTypes = null; + + if (fields != null && fields.size() > 0) { + // SEARCH FOR FIELD TYPES IF ANY + for (int i = 0; i < fields.size(); i += 2) { + final String fieldName = OIOUtils.getStringContent(fields.get(i)); + final String fieldValue = fields.get(i + 1); + final String fieldValueAsString = OIOUtils.getStringContent(fieldValue); + + if (fieldName.equals(ATTRIBUTE_FIELD_TYPES) && iRecord instanceof ODocument) { + fieldTypes = loadFieldTypes(fieldTypes, fieldValueAsString); + } else if (fieldName.equals(ODocumentHelper.ATTRIBUTE_TYPE)) { + if (iRecord == null || ORecordInternal.getRecordType(iRecord) != fieldValueAsString.charAt(0)) { + // CREATE THE RIGHT RECORD INSTANCE + iRecord = Orient.instance().getRecordFactoryManager().newInstance((byte) fieldValueAsString.charAt(0)); + } + } else if (needReload && fieldName.equals(ODocumentHelper.ATTRIBUTE_RID) && iRecord instanceof ODocument) { + if (fieldValue != null && fieldValue.length() > 0) { + ORecord localRecord = ODatabaseRecordThreadLocal.INSTANCE.get().load(new ORecordId(fieldValueAsString)); + if (localRecord != null) + iRecord = localRecord; + } + } else if (fieldName.equals(ODocumentHelper.ATTRIBUTE_CLASS) && iRecord instanceof ODocument) { + className = "null".equals(fieldValueAsString) ? null : fieldValueAsString; + ODocumentInternal.fillClassNameIfNeeded(((ODocument) iRecord),className); + + } + } + + if (iRecord == null) + iRecord = new ODocument(); + + try { + for (int i = 0; i < fields.size(); i += 2) { + final String fieldName = OIOUtils.getStringContent(fields.get(i)); + final String fieldValue = fields.get(i + 1); + final String fieldValueAsString = OIOUtils.getStringContent(fieldValue); + + // RECORD ATTRIBUTES + if (fieldName.equals(ODocumentHelper.ATTRIBUTE_RID)) + ORecordInternal.setIdentity(iRecord, new ORecordId(fieldValueAsString)); + else if (fieldName.equals(ODocumentHelper.ATTRIBUTE_VERSION)) + ORecordInternal.setVersion(iRecord, Integer.parseInt(fieldValue)); + else if (fieldName.equals(ODocumentHelper.ATTRIBUTE_CLASS)) { + continue; + } else if (fieldName.equals(ODocumentHelper.ATTRIBUTE_TYPE)) { + continue; + } else if (fieldName.equals(ATTRIBUTE_FIELD_TYPES) && iRecord instanceof ODocument) { + continue; + } else if (fieldName.equals("value") && !(iRecord instanceof ODocument)) { + // RECORD VALUE(S) + if ("null".equals(fieldValue)) + iRecord.fromStream(OCommonConst.EMPTY_BYTE_ARRAY); + else if (iRecord instanceof OBlob) { + // BYTES + iRecord.fromStream(OBase64Utils.decode(fieldValueAsString)); + } else if (iRecord instanceof ORecordStringable) { + ((ORecordStringable) iRecord).value(fieldValueAsString); + } else + throw new IllegalArgumentException("unsupported type of record"); + } else if (iRecord instanceof ODocument) { + final ODocument doc = ((ODocument) iRecord); + + // DETERMINE THE TYPE FROM THE SCHEMA + OType type = determineType(doc, fieldName); + + final Object v = getValue(doc, fieldName, fieldValue, fieldValueAsString, type, null, fieldTypes, noMap, iOptions); + + if (v != null) + if (v instanceof Collection && !((Collection) v).isEmpty()) { + if (v instanceof ORecordLazyMultiValue) + ((ORecordLazyMultiValue) v).setAutoConvertToRecord(false); + + // CHECK IF THE COLLECTION IS EMBEDDED + if (type == null) { + // TRY TO UNDERSTAND BY FIRST ITEM + Object first = ((Collection) v).iterator().next(); + if (first != null && first instanceof ORecord && !((ORecord) first).getIdentity().isValid()) + type = v instanceof Set ? OType.EMBEDDEDSET : OType.EMBEDDEDLIST; + } + + if (type != null) { + // TREAT IT AS EMBEDDED + doc.field(fieldName, v, type); + continue; + } + } else if (v instanceof Map && !((Map) v).isEmpty()) { + // CHECK IF THE MAP IS EMBEDDED + Object first = ((Map) v).values().iterator().next(); + if (first != null && first instanceof ORecord && !((ORecord) first).getIdentity().isValid()) { + doc.field(fieldName, v, OType.EMBEDDEDMAP); + continue; + } + } else if (v instanceof ODocument && type != null && type.isLink()) { + String className1 = ((ODocument) v).getClassName(); + if (className1 != null && className1.length() > 0) + ((ODocument) v).save(); + } + + if (type == null && fieldTypes != null && fieldTypes.containsKey(fieldName)) + type = ORecordSerializerStringAbstract.getType(fieldValue, fieldTypes.get(fieldName)); + + if (v instanceof OTrackedSet) { + if (OMultiValue.getFirstValue((Set) v) instanceof OIdentifiable) + type = OType.LINKSET; + } else if (v instanceof OTrackedList) { + if (OMultiValue.getFirstValue((List) v) instanceof OIdentifiable) + type = OType.LINKLIST; + } + + if (type != null) + doc.field(fieldName, v, type); + else + doc.field(fieldName, v); + } + + } + if (className != null) { + //Trigger the default value + ((ODocument) iRecord).setClassName(className); + } + } catch (Exception e) { + if (iRecord.getIdentity().isValid()) + throw OException.wrapException( + new OSerializationException("Error on unmarshalling JSON content for record " + iRecord.getIdentity()), e); + else + throw OException.wrapException(new OSerializationException("Error on unmarshalling JSON content for record: " + iSource), + e); + } + + } + + return iRecord; + } + + @Override + public byte[] writeClassOnly(ORecord iSource) { + return new byte[] {}; + } + + @Override + public StringBuilder toString(final ORecord iRecord, final StringBuilder iOutput, final String iFormat, + final OUserObject2RecordHandler iObjHandler, boolean iOnlyDelta, boolean autoDetectCollectionType) { + try { + final StringWriter buffer = new StringWriter(INITIAL_SIZE); + final OJSONWriter json = new OJSONWriter(buffer, iFormat); + final FormatSettings settings = new FormatSettings(iFormat); + + json.beginObject(); + + OJSONFetchContext context = new OJSONFetchContext(json, settings); + context.writeSignature(json, iRecord); + + if (iRecord instanceof ODocument) { + final OFetchPlan fp = OFetchHelper.buildFetchPlan(settings.fetchPlan); + + OFetchHelper.fetch(iRecord, null, fp, new OJSONFetchListener(), context, iFormat); + } else if (iRecord instanceof ORecordStringable) { + + // STRINGABLE + final ORecordStringable record = (ORecordStringable) iRecord; + json.writeAttribute(settings.indentLevel, true, "value", record.value()); + + } else if (iRecord instanceof OBlob) { + // BYTES + final OBlob record = (OBlob) iRecord; + json.writeAttribute(settings.indentLevel, true, "value", OBase64Utils.encodeBytes(record.toStream())); + } else + + throw new OSerializationException( + "Error on marshalling record of type '" + iRecord.getClass() + "' to JSON. The record type cannot be exported to JSON"); + + json.endObject(settings.indentLevel, true); + + iOutput.append(buffer); + return iOutput; + } catch (IOException e) { + throw OException.wrapException(new OSerializationException("Error on marshalling of record to JSON"), e); + } + } + + @Override + public String toString() { + return NAME; + } + + private OType determineType(ODocument doc, String fieldName) { + OType type = null; + final OClass cls = ODocumentInternal.getImmutableSchemaClass(doc); + if (cls != null) { + final OProperty prop = cls.getProperty(fieldName); + if (prop != null) + type = prop.getType(); + } + return type; + } + + private Map loadFieldTypes(Map fieldTypes, String fieldValueAsString) { + // LOAD THE FIELD TYPE MAP + final String[] fieldTypesParts = fieldValueAsString.split(","); + if (fieldTypesParts.length > 0) { + fieldTypes = new HashMap(); + String[] part; + for (String f : fieldTypesParts) { + part = f.split("="); + if (part.length == 2) + fieldTypes.put(part[0], part[1].charAt(0)); + } + } + return fieldTypes; + } + + private String unwrapSource(String iSource) { + if (iSource == null) + throw new OSerializationException("Error on unmarshalling JSON content: content is null"); + + iSource = iSource.trim(); + if (!iSource.startsWith("{") || !iSource.endsWith("}")) + throw new OSerializationException("Error on unmarshalling JSON content '" + iSource + "': content must be between { }"); + + iSource = iSource.substring(1, iSource.length() - 1).trim(); + return iSource; + } + + @SuppressWarnings("unchecked") + private Object getValue(final ODocument iRecord, String iFieldName, String iFieldValue, String iFieldValueAsString, OType iType, + OType iLinkedType, final Map iFieldTypes, final boolean iNoMap, final String iOptions) { + if (iFieldValue.equals("null")) + return null; + + if (iFieldName != null && ODocumentInternal.getImmutableSchemaClass(iRecord) != null) { + final OProperty p = ODocumentInternal.getImmutableSchemaClass(iRecord).getProperty(iFieldName); + if (p != null) { + iType = p.getType(); + iLinkedType = p.getLinkedType(); + } + } + + if (iType == null && iFieldTypes != null && iFieldTypes.containsKey(iFieldName)) + iType = ORecordSerializerStringAbstract.getType(iFieldValue, iFieldTypes.get(iFieldName)); + + if (iFieldValue.startsWith("{") && iFieldValue.endsWith("}")) { + return getValueAsObjectOrMap(iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, iOptions); + } else if (iFieldValue.startsWith("[") && iFieldValue.endsWith("]")) { + return getValueAsCollection(iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, iOptions); + } + + if (iType == null || iType == OType.ANY) + // TRY TO DETERMINE THE CONTAINED TYPE from THE FIRST VALUE + if (iFieldValue.charAt(0) != '\"' && iFieldValue.charAt(0) != '\'') { + if (iFieldValue.equalsIgnoreCase("false") || iFieldValue.equalsIgnoreCase("true")) + iType = OType.BOOLEAN; + else { + Character c = null; + if (iFieldTypes != null) { + c = iFieldTypes.get(iFieldName); + if (c != null) + iType = ORecordSerializerStringAbstract.getType(iFieldValue + c); + } + + if (c == null && !iFieldValue.isEmpty()) { + // TRY TO AUTODETERMINE THE BEST TYPE + if (ORecordId.isA(iFieldValue)) + iType = OType.LINK; + else if (iFieldValue.matches(".*[\\.Ee].*")) { + // DECIMAL FORMAT: DETERMINE IF DOUBLE OR FLOAT + final Double v = new Double(OIOUtils.getStringContent(iFieldValue)); + + return v; + // REMOVED TRUNK to float + // if (canBeTrunkedToFloat(v)) + // return v.floatValue(); + // else + // return v; + } else { + final Long v = new Long(OIOUtils.getStringContent(iFieldValue)); + // INTEGER FORMAT: DETERMINE IF DOUBLE OR FLOAT + + if (canBeTrunkedToInt(v)) + return v.intValue(); + else + return v; + } + } + } + } else if (iFieldValue.startsWith("{") && iFieldValue.endsWith("}")) + iType = OType.EMBEDDED; + else { + if (ORecordId.isA(iFieldValueAsString)) + iType = OType.LINK; + + if (iFieldTypes != null) { + Character c = iFieldTypes.get(iFieldName); + if (c != null) + iType = ORecordSerializerStringAbstract.getType(iFieldValueAsString, c); + } + + if (iType == null) + iType = OType.STRING; + } + + if (iType != null) + switch (iType) { + case STRING: + return decodeJSON(iFieldValueAsString); + + case LINK: + final int pos = iFieldValueAsString.indexOf('@'); + if (pos > -1) + // CREATE DOCUMENT + return new ODocument(iFieldValueAsString.substring(1, pos), new ORecordId(iFieldValueAsString.substring(pos + 1))); + else { + // CREATE SIMPLE RID + return new ORecordId(iFieldValueAsString); + } + + case EMBEDDED: + return fromString(iFieldValueAsString); + + case DATE: + if (iFieldValueAsString == null || iFieldValueAsString.equals("")) + return null; + try { + // TRY TO PARSE AS LONG + return Long.parseLong(iFieldValueAsString); + } catch (NumberFormatException e) { + try { + // TRY TO PARSE AS DATE + return ODateHelper.getDateFormatInstance().parseObject(iFieldValueAsString); + } catch (ParseException ex) { + throw OException.wrapException(new OSerializationException( + "Unable to unmarshall date (format=" + ODateHelper.getDateFormat() + ") : " + iFieldValueAsString), e); + } + } + + case DATETIME: + if (iFieldValueAsString == null || iFieldValueAsString.equals("")) + return null; + try { + // TRY TO PARSE AS LONG + return Long.parseLong(iFieldValueAsString); + } catch (NumberFormatException e) { + try { + // TRY TO PARSE AS DATETIME + return ODateHelper.getDateTimeFormatInstance().parseObject(iFieldValueAsString); + } catch (ParseException ex) { + throw OException + .wrapException( + new OSerializationException( + "Unable to unmarshall datetime (format=" + ODateHelper.getDateTimeFormat() + ") : " + iFieldValueAsString), + e); + } + } + case BINARY: + return OStringSerializerHelper.fieldTypeFromStream(iRecord, iType, iFieldValueAsString); + case CUSTOM: { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(OBase64Utils.decode(iFieldValueAsString)); + ObjectInputStream input = new ObjectInputStream(bais); + return input.readObject(); + } catch (IOException e) { + throw OException.wrapException(new OSerializationException("Error on custom field deserialization"), e); + } catch (ClassNotFoundException e) { + throw OException.wrapException(new OSerializationException("Error on custom field deserialization"), e); + } + } + default: + return OStringSerializerHelper.fieldTypeFromStream(iRecord, iType, iFieldValue); + } + + return iFieldValueAsString; + } + + private boolean canBeTrunkedToInt(Long v) { + return (v > 0) ? v.compareTo(MAX_INT) <= 0 : v.compareTo(MIN_INT) >= 0; + } + + private boolean canBeTrunkedToFloat(Double v) { + // TODO not really correct check. Small numbers with high precision will be trunked while they shouldn't be + + return (v > 0) ? v.compareTo(MAX_FLOAT) <= 0 : v.compareTo(MIN_FLOAT) >= 0; + } + + /** + * OBJECT OR MAP. CHECK THE TYPE ATTRIBUTE TO KNOW IT. + */ + private Object getValueAsObjectOrMap(ODocument iRecord, String iFieldValue, OType iType, OType iLinkedType, + Map iFieldTypes, boolean iNoMap, String iOptions) { + final String[] fields = OStringParser.getWords(iFieldValue.substring(1, iFieldValue.length() - 1), ":,", true); + + if (fields == null || fields.length == 0) + if (iNoMap) { + ODocument res = new ODocument(); + ODocumentInternal.addOwner(res, iRecord); + return res; + } else + return new HashMap(); + + if (iNoMap || hasTypeField(fields)) { + return getValueAsRecord(iRecord, iFieldValue, iType, iOptions, fields); + } else { + return getValueAsMap(iRecord, iFieldValue, iLinkedType, iFieldTypes, false, iOptions, fields); + } + } + + private Object getValueAsMap(ODocument iRecord, String iFieldValue, OType iLinkedType, Map iFieldTypes, + boolean iNoMap, String iOptions, String[] fields) { + if (fields.length % 2 == 1) + throw new OSerializationException("Bad JSON format on map. Expected pairs of field:value but received '" + iFieldValue + "'"); + + final Map embeddedMap = new LinkedHashMap(); + + for (int i = 0; i < fields.length; i += 2) { + String iFieldName = fields[i]; + if (iFieldName.length() >= 2) + iFieldName = iFieldName.substring(1, iFieldName.length() - 1); + iFieldValue = fields[i + 1]; + final String valueAsString = OIOUtils.getStringContent(iFieldValue); + + embeddedMap.put(iFieldName, + getValue(iRecord, null, iFieldValue, valueAsString, iLinkedType, null, iFieldTypes, iNoMap, iOptions)); + } + return embeddedMap; + } + + private Object getValueAsRecord(ODocument iRecord, String iFieldValue, OType iType, String iOptions, String[] fields) { + ORID rid = new ORecordId(OIOUtils.getStringContent(getFieldValue("@rid", fields))); + boolean shouldReload = rid.isTemporary(); + + final ODocument recordInternal = (ODocument) fromString(iFieldValue, new ODocument(), null, iOptions, shouldReload); + + if (shouldBeDeserializedAsEmbedded(recordInternal, iType)) + ODocumentInternal.addOwner(recordInternal, iRecord); + else { + ODatabaseDocument database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + + if (rid.isPersistent() && database != null) { + ODocument documentToMerge = database.load(rid); + documentToMerge.merge(recordInternal, false, false); + return documentToMerge; + } + } + + return recordInternal; + } + + private Object getValueAsCollection(ODocument iRecord, String iFieldValue, OType iType, OType iLinkedType, + Map iFieldTypes, boolean iNoMap, String iOptions) { + // remove square brackets + iFieldValue = iFieldValue.substring(1, iFieldValue.length() - 1); + + if (iType == OType.LINKBAG) { + final ORidBag bag = new ORidBag(); + + parseCollection(iRecord, iFieldValue, iType, OType.LINK, iFieldTypes, iNoMap, iOptions, new CollectionItemVisitor() { + @Override + public void visitItem(Object item) { + bag.add((OIdentifiable) item); + } + }); + + return bag; + } else if (iType == OType.LINKSET) { + return getValueAsLinkedCollection(new ORecordLazySet(iRecord), iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, + iOptions); + } else if (iType == OType.LINKLIST) { + return getValueAsLinkedCollection(new ORecordLazyList(iRecord), iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, + iOptions); + } else if (iType == OType.EMBEDDEDSET) { + return getValueAsEmbeddedCollection(new OTrackedSet(iRecord), iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, + iNoMap, iOptions); + } else { + return getValueAsEmbeddedCollection(new OTrackedList(iRecord), iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, + iNoMap, iOptions); + } + } + + private Object getValueAsLinkedCollection(final Collection collection, ODocument iRecord, String iFieldValue, + OType iType, OType iLinkedType, Map iFieldTypes, boolean iNoMap, String iOptions) { + + parseCollection(iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, iOptions, new CollectionItemVisitor() { + @Override + public void visitItem(Object item) { + collection.add((OIdentifiable) item); + } + }); + + return collection; + } + + private Object getValueAsEmbeddedCollection(final Collection collection, ODocument iRecord, String iFieldValue, + OType iType, OType iLinkedType, Map iFieldTypes, boolean iNoMap, String iOptions) { + + parseCollection(iRecord, iFieldValue, iType, iLinkedType, iFieldTypes, iNoMap, iOptions, new CollectionItemVisitor() { + @Override + public void visitItem(Object item) { + collection.add(item); + } + }); + + return collection; + } + + private void parseCollection(ODocument iRecord, String iFieldValue, OType iType, OType iLinkedType, + Map iFieldTypes, boolean iNoMap, String iOptions, CollectionItemVisitor visitor) { + if (!iFieldValue.isEmpty()) { + for (String item : OStringSerializerHelper.smartSplit(iFieldValue, ',')) { + final String itemValue = item.trim(); + if (itemValue.length() == 0) + continue; + + final Object collectionItem = getValue(iRecord, null, itemValue, OIOUtils.getStringContent(itemValue), iLinkedType, null, + iFieldTypes, iNoMap, iOptions); + + // TODO redundant in some cases, owner is already added by getValue in some cases + if (shouldBeDeserializedAsEmbedded(collectionItem, iType)) + ODocumentInternal.addOwner((ODocument) collectionItem, iRecord); + + visitor.visitItem(collectionItem); + } + } + } + + private boolean shouldBeDeserializedAsEmbedded(Object record, OType iType) { + return record instanceof ODocument && !((ODocument) record).getIdentity().isTemporary() + && !((ODocument) record).getIdentity().isPersistent() && (iType == null || !iType.isLink()); + } + + private String decodeJSON(String iFieldValueAsString) { + if (iFieldValueAsString == null) { + return null; + } + StringBuilder builder = new StringBuilder(iFieldValueAsString.length()); + boolean quoting = false; + for (char c : iFieldValueAsString.toCharArray()) { + if (quoting) { + if (c != '\\' && c != '\"' && c != '/') { + builder.append('\\'); + } + builder.append(c); + quoting = false; + } else { + if (c == '\\') { + quoting = true; + } else { + builder.append(c); + } + } + } + return builder.toString(); + } + + private boolean hasTypeField(final String[] fields) { + return hasField("@type", fields); + } + + /** + * Checks if given collection of fields contain field with specified name. + * + * @param field + * to find + * @param fields + * collection of fields where search + * @return true if collection contain specified field, false otherwise. + */ + private boolean hasField(final String field, final String[] fields) { + return getFieldValue(field, fields) != null; + } + + private String getFieldValue(final String field, final String[] fields) { + String doubleQuotes = "\"" + field + "\""; + String singleQuotes = "'" + field + "'"; + for (int i = 0; i < fields.length; i = i + 2) { + if (fields[i].equals(doubleQuotes) || fields[i].equals(singleQuotes)) { + return fields[i + 1]; + } + } + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerSchemaAware2CSV.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerSchemaAware2CSV.java new file mode 100755 index 00000000000..b488970ee8e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerSchemaAware2CSV.java @@ -0,0 +1,581 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record.string; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.object.ODatabaseObject; +import com.orientechnologies.orient.core.db.record.ORecordLazyMap; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ORecordSerializerSchemaAware2CSV extends ORecordSerializerCSVAbstract { + public static final String NAME = "ORecordDocument2csv"; + public static final ORecordSerializerSchemaAware2CSV INSTANCE = new ORecordSerializerSchemaAware2CSV(); + private static final long serialVersionUID = 1L; + + @Override + public String toString() { + return NAME; + } + + @Override + public int getCurrentVersion() { + return 0; + } + + @Override + public int getMinSupportedVersion() { + return 0; + } + + public String getClassName(String content) { + content = content.trim(); + + if (content.length() == 0) + return null; + + final int posFirstValue = content.indexOf(OStringSerializerHelper.ENTRY_SEPARATOR); + final int pos = content.indexOf(OStringSerializerHelper.CLASS_SEPARATOR); + + if (pos > -1 && (pos < posFirstValue || posFirstValue == -1)) + return content.substring(0, pos); + + return null; + } + + @Override + public ORecord fromString(String iContent, final ORecord iRecord, final String[] iFields) { + iContent = iContent.trim(); + + if (iContent.length() == 0) + return iRecord; + + // UNMARSHALL THE CLASS NAME + final ODocument record = (ODocument) iRecord; + + int pos; + final ODatabaseDocumentInternal database = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + final int posFirstValue = iContent.indexOf(OStringSerializerHelper.ENTRY_SEPARATOR); + pos = iContent.indexOf(OStringSerializerHelper.CLASS_SEPARATOR); + if (pos > -1 && (pos < posFirstValue || posFirstValue == -1)) { + if ((record.getIdentity().getClusterId() < 0 || database == null + || !database.getStorageVersions().classesAreDetectedByClusterId())) + ODocumentInternal.fillClassNameIfNeeded(((ODocument) iRecord), iContent.substring(0, pos)); + iContent = iContent.substring(pos + 1); + } else + record.setClassNameIfExists(null); + + if (iFields != null && iFields.length == 1 && iFields[0].equals("@class")) + // ONLY THE CLASS NAME HAS BEEN REQUESTED: RETURN NOW WITHOUT UNMARSHALL THE ENTIRE RECORD + return iRecord; + + final List fields = OStringSerializerHelper.smartSplit(iContent, OStringSerializerHelper.RECORD_SEPARATOR, true, true); + + String fieldName = null; + String fieldValue; + OType type; + OClass linkedClass; + OType linkedType; + OProperty prop; + + final Set fieldSet; + + if (iFields != null && iFields.length > 0) { + fieldSet = new HashSet(iFields.length); + for (String f : iFields) + fieldSet.add(f); + } else + fieldSet = null; + + // UNMARSHALL ALL THE FIELDS + for (String fieldEntry : fields) { + fieldEntry = fieldEntry.trim(); + boolean uncertainType = false; + + try { + pos = fieldEntry.indexOf(FIELD_VALUE_SEPARATOR); + if (pos > -1) { + // GET THE FIELD NAME + fieldName = fieldEntry.substring(0, pos); + + // CHECK IF THE FIELD IS REQUESTED TO BEING UNMARSHALLED + if (fieldSet != null && !fieldSet.contains(fieldName)) + continue; + + if (record.containsField(fieldName)) + // ALREADY UNMARSHALLED: DON'T OVERWRITE IT + continue; + + // GET THE FIELD VALUE + fieldValue = fieldEntry.length() > pos + 1 ? fieldEntry.substring(pos + 1) : null; + + boolean setFieldType = false; + + // SEARCH FOR A CONFIGURED PROPERTY + prop = ODocumentInternal.getImmutableSchemaClass(record) != null ? + ODocumentInternal.getImmutableSchemaClass(record).getProperty(fieldName) : + null; + if (prop != null && prop.getType() != OType.ANY) { + // RECOGNIZED PROPERTY + type = prop.getType(); + linkedClass = prop.getLinkedClass(); + linkedType = prop.getLinkedType(); + + } else { + // SCHEMA PROPERTY NOT FOUND FOR THIS FIELD: TRY TO AUTODETERMINE THE BEST TYPE + type = record.fieldType(fieldName); + if (type == OType.ANY) + type = null; + if (type != null) + setFieldType = true; + linkedClass = null; + linkedType = null; + + // NOT FOUND: TRY TO DETERMINE THE TYPE FROM ITS CONTENT + if (fieldValue != null && type == null) { + if (fieldValue.length() > 1 && fieldValue.charAt(0) == '"' && fieldValue.charAt(fieldValue.length() - 1) == '"') { + type = OType.STRING; + } else if (fieldValue.startsWith(OStringSerializerHelper.LINKSET_PREFIX)) { + type = OType.LINKSET; + } else if (fieldValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN + && fieldValue.charAt(fieldValue.length() - 1) == OStringSerializerHelper.LIST_END + || fieldValue.charAt(0) == OStringSerializerHelper.SET_BEGIN + && fieldValue.charAt(fieldValue.length() - 1) == OStringSerializerHelper.SET_END) { + // EMBEDDED LIST/SET + type = fieldValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN ? OType.EMBEDDEDLIST : OType.EMBEDDEDSET; + + final String value = fieldValue.substring(1, fieldValue.length() - 1); + + if (!value.isEmpty()) { + if (value.charAt(0) == OStringSerializerHelper.LINK) { + // TODO replace with regex + // ASSURE ALL THE ITEMS ARE RID + int max = value.length(); + boolean allLinks = true; + boolean checkRid = true; + for (int i = 0; i < max; ++i) { + char c = value.charAt(i); + if (checkRid) { + if (c != '#') { + allLinks = false; + break; + } + checkRid = false; + } else if (c == ',') + checkRid = true; + } + + if (allLinks) { + type = fieldValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN ? OType.LINKLIST : OType.LINKSET; + linkedType = OType.LINK; + } + } else if (value.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN) { + linkedType = OType.EMBEDDED; + } else if (value.charAt(0) == OStringSerializerHelper.CUSTOM_TYPE) { + linkedType = OType.CUSTOM; + } else if (Character.isDigit(value.charAt(0)) || value.charAt(0) == '+' || value.charAt(0) == '-') { + String[] items = value.split(","); + linkedType = getType(items[0]); + } else if (value.charAt(0) == '\'' || value.charAt(0) == '"') + linkedType = OType.STRING; + } else + uncertainType = true; + + } else if (fieldValue.charAt(0) == OStringSerializerHelper.MAP_BEGIN + && fieldValue.charAt(fieldValue.length() - 1) == OStringSerializerHelper.MAP_END) { + type = OType.EMBEDDEDMAP; + } else if (fieldValue.charAt(0) == OStringSerializerHelper.LINK) + type = OType.LINK; + else if (fieldValue.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN) { + // TEMPORARY PATCH + if (fieldValue.startsWith("(ORIDs")) + type = OType.LINKSET; + else + type = OType.EMBEDDED; + } else if (fieldValue.charAt(0) == OStringSerializerHelper.BAG_BEGIN) { + type = OType.LINKBAG; + } else if (fieldValue.equals("true") || fieldValue.equals("false")) + type = OType.BOOLEAN; + else + type = getType(fieldValue); + } + } + final Object value = fieldFromStream(iRecord, type, linkedClass, linkedType, fieldName, fieldValue); + if ("@class".equals(fieldName)) { + ODocumentInternal.fillClassNameIfNeeded(((ODocument) iRecord), value.toString()); + } else { + record.field(fieldName, value, type); + } + + if (uncertainType) + record.setFieldType(fieldName, null); + } + } catch (Exception e) { + throw OException.wrapException(new OSerializationException( + "Error on unmarshalling field '" + fieldName + "' in record " + iRecord.getIdentity() + " with value: " + fieldEntry), + e); + } + } + + return iRecord; + } + + @Override + public byte[] toStream(ORecord iRecord, boolean iOnlyDelta) { + final byte[] result = super.toStream(iRecord, iOnlyDelta); + if (result == null || result.length > 0) + return result; + + // Fix of nasty IBM JDK bug. In case of very depth recursive graph serialization + // ODocument#_source property may be initialized incorrectly. + final ODocument recordSchemaAware = (ODocument) iRecord; + if (recordSchemaAware.fields() > 0) + return null; + + return result; + } + + public byte[] writeClassOnly(ORecord iSource) { + final ODocument record = (ODocument) iSource; + StringBuilder iOutput = new StringBuilder(); + if (ODocumentInternal.getImmutableSchemaClass(record) != null) { + iOutput.append(ODocumentInternal.getImmutableSchemaClass(record).getStreamableName()); + iOutput.append(OStringSerializerHelper.CLASS_SEPARATOR); + } + try { + return iOutput.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("error writing class name"), e); + } + } + + @Override + protected StringBuilder toString(ORecord iRecord, final StringBuilder iOutput, final String iFormat, + OUserObject2RecordHandler iObjHandler, final boolean iOnlyDelta, final boolean autoDetectCollectionType) { + if (iRecord == null) + throw new OSerializationException("Expected a record but was null"); + + if (!(iRecord instanceof ODocument)) + throw new OSerializationException("Cannot marshall a record of type " + iRecord.getClass().getSimpleName()); + + final ODocument record = (ODocument) iRecord; + + if (!iOnlyDelta && ODocumentInternal.getImmutableSchemaClass(record) != null) { + iOutput.append(ODocumentInternal.getImmutableSchemaClass(record).getStreamableName()); + iOutput.append(OStringSerializerHelper.CLASS_SEPARATOR); + } + + + OProperty prop; + OType type; + OClass linkedClass; + OType linkedType; + String fieldClassName; + int i = 0; + + final String[] fieldNames = iOnlyDelta && record.isTrackingChanges() ? record.getDirtyFields() : record.fieldNames(); + + // MARSHALL ALL THE FIELDS OR DELTA IF TRACKING IS ENABLED + for (String fieldName : fieldNames) { + Object fieldValue = record.rawField(fieldName); + if (i > 0) + iOutput.append(OStringSerializerHelper.RECORD_SEPARATOR); + + // SEARCH FOR A CONFIGURED PROPERTY + prop = ODocumentInternal.getImmutableSchemaClass(record) != null ? + ODocumentInternal.getImmutableSchemaClass(record).getProperty(fieldName) : + null; + fieldClassName = getClassName(fieldValue); + + type = record.fieldType(fieldName); + if (type == OType.ANY) + type = null; + + linkedClass = null; + linkedType = null; + + if (prop != null && prop.getType() != OType.ANY) { + // RECOGNIZED PROPERTY + type = prop.getType(); + linkedClass = prop.getLinkedClass(); + linkedType = prop.getLinkedType(); + + } else if (fieldValue != null) { + // NOT FOUND: TRY TO DETERMINE THE TYPE FROM ITS CONTENT + if (type == null) { + if (fieldValue.getClass() == byte[].class) + type = OType.BINARY; + else if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && fieldValue instanceof ORecord) { + if (type == null) + // DETERMINE THE FIELD TYPE + if (fieldValue instanceof ODocument && ((ODocument) fieldValue).hasOwners()) + type = OType.EMBEDDED; + else + type = OType.LINK; + + linkedClass = getLinkInfo(ODatabaseRecordThreadLocal.INSTANCE.get(), fieldClassName); + } else if (fieldValue instanceof ORID) + // DETERMINE THE FIELD TYPE + type = OType.LINK; + + else if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && ODatabaseRecordThreadLocal.INSTANCE.get() + .getDatabaseOwner() instanceof ODatabaseObject && + ((ODatabaseObject) ODatabaseRecordThreadLocal.INSTANCE.get().getDatabaseOwner()).getEntityManager() + .getEntityClass(fieldClassName) != null) { + // DETERMINE THE FIELD TYPE + type = OType.LINK; + linkedClass = getLinkInfo(ODatabaseRecordThreadLocal.INSTANCE.get(), fieldClassName); + } else if (fieldValue instanceof Date) + type = OType.DATETIME; + else if (fieldValue instanceof String) + type = OType.STRING; + else if (fieldValue instanceof Integer || fieldValue instanceof BigInteger) + type = OType.INTEGER; + else if (fieldValue instanceof Long) + type = OType.LONG; + else if (fieldValue instanceof Float) + type = OType.FLOAT; + else if (fieldValue instanceof Short) + type = OType.SHORT; + else if (fieldValue instanceof Byte) + type = OType.BYTE; + else if (fieldValue instanceof Double) + type = OType.DOUBLE; + else if (fieldValue instanceof BigDecimal) + type = OType.DECIMAL; + else if (fieldValue instanceof ORidBag) + type = OType.LINKBAG; + + if (fieldValue instanceof OMultiCollectionIterator) { + type = ((OMultiCollectionIterator) fieldValue).isEmbedded() ? OType.EMBEDDEDLIST : OType.LINKLIST; + linkedType = ((OMultiCollectionIterator) fieldValue).isEmbedded() ? OType.EMBEDDED : OType.LINK; + } else if (fieldValue instanceof Collection || fieldValue.getClass().isArray()) { + final int size = OMultiValue.getSize(fieldValue); + + Boolean autoConvertLinks = null; + if (fieldValue instanceof ORecordLazyMultiValue) { + autoConvertLinks = ((ORecordLazyMultiValue) fieldValue).isAutoConvertToRecord(); + if (autoConvertLinks) + // DISABLE AUTO CONVERT + ((ORecordLazyMultiValue) fieldValue).setAutoConvertToRecord(false); + } + + if (autoDetectCollectionType) + if (size > 0) { + final Object firstValue = OMultiValue.getFirstValue(fieldValue); + + if (firstValue != null) { + if (firstValue instanceof ORID) { + linkedClass = null; + linkedType = OType.LINK; + if (fieldValue instanceof Set) + type = OType.LINKSET; + else + type = OType.LINKLIST; + } else if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && (firstValue instanceof ODocument + && !((ODocument) firstValue).isEmbedded()) && (firstValue instanceof ORecord || ( + ODatabaseRecordThreadLocal.INSTANCE.get().getDatabaseOwner() instanceof ODatabaseObject + && ((ODatabaseObject) ODatabaseRecordThreadLocal.INSTANCE.get().getDatabaseOwner()).getEntityManager() + .getEntityClass(getClassName(firstValue)) != null))) { + linkedClass = getLinkInfo(ODatabaseRecordThreadLocal.INSTANCE.get(), getClassName(firstValue)); + if (type == null) { + // LINK: GET THE CLASS + linkedType = OType.LINK; + + if (fieldValue instanceof Set) + type = OType.LINKSET; + else + type = OType.LINKLIST; + } else + linkedType = OType.EMBEDDED; + } else { + // EMBEDDED COLLECTION + if (firstValue instanceof ODocument && ((((ODocument) firstValue).hasOwners()) || type == OType.EMBEDDEDSET + || type == OType.EMBEDDEDLIST || type == OType.EMBEDDEDMAP)) + linkedType = OType.EMBEDDED; + else if (firstValue instanceof Enum) + linkedType = OType.STRING; + else { + linkedType = OType.getTypeByClass(firstValue.getClass()); + + if (linkedType != OType.LINK) + // EMBEDDED FOR SURE DON'T USE THE LINKED TYPE + linkedType = null; + } + + if (type == null) + if (fieldValue instanceof ORecordLazySet) + type = OType.LINKSET; + else if (fieldValue instanceof Set) + type = OType.EMBEDDEDSET; + else + type = OType.EMBEDDEDLIST; + } + } + } else if (type == null) + type = OType.EMBEDDEDLIST; + + if (fieldValue instanceof ORecordLazyMultiValue && autoConvertLinks) { + // REPLACE PREVIOUS SETTINGS + ((ORecordLazyMultiValue) fieldValue).setAutoConvertToRecord(true); + } + + } else if (fieldValue instanceof Map && type == null) { + final int size = OMultiValue.getSize(fieldValue); + + Boolean autoConvertLinks = null; + if (fieldValue instanceof ORecordLazyMap) { + autoConvertLinks = ((ORecordLazyMap) fieldValue).isAutoConvertToRecord(); + if (autoConvertLinks) + // DISABLE AUTO CONVERT + ((ORecordLazyMap) fieldValue).setAutoConvertToRecord(false); + } + + if (size > 0) { + final Object firstValue = OMultiValue.getFirstValue(fieldValue); + + if (firstValue != null) { + if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && (firstValue instanceof ODocument && !((ODocument) firstValue) + .isEmbedded()) && (firstValue instanceof ORecord || ( + ODatabaseRecordThreadLocal.INSTANCE.get().getDatabaseOwner() instanceof ODatabaseObject && + ((ODatabaseObject) ODatabaseRecordThreadLocal.INSTANCE.get().getDatabaseOwner()).getEntityManager() + .getEntityClass(getClassName(firstValue)) != null))) { + linkedClass = getLinkInfo(ODatabaseRecordThreadLocal.INSTANCE.get(), getClassName(firstValue)); + // LINK: GET THE CLASS + linkedType = OType.LINK; + type = OType.LINKMAP; + } + } + } + + if (type == null) + type = OType.EMBEDDEDMAP; + + if (fieldValue instanceof ORecordLazyMap && autoConvertLinks) + // REPLACE PREVIOUS SETTINGS + ((ORecordLazyMap) fieldValue).setAutoConvertToRecord(true); + } + } + } + + if (type == OType.TRANSIENT) + // TRANSIENT FIELD + continue; + + if (type == null) + type = OType.EMBEDDED; + + iOutput.append(fieldName); + iOutput.append(FIELD_VALUE_SEPARATOR); + fieldToStream(record, iOutput, iObjHandler, type, linkedClass, linkedType, fieldName, fieldValue, true); + + i++; + } + + // GET THE OVERSIZE IF ANY + final float overSize; + if (ODocumentInternal.getImmutableSchemaClass(record) != null) + // GET THE CONFIGURED OVERSIZE SETTED PER CLASS + overSize = ODocumentInternal.getImmutableSchemaClass(record).getOverSize(); + else + overSize = 0; + + // APPEND BLANKS IF NEEDED + final int newSize; + if (record.hasOwners()) + // EMBEDDED: GET REAL SIZE + newSize = iOutput.length(); + else if (record.getSize() == iOutput.length()) + // IDENTICAL! DO NOTHING + newSize = record.getSize(); + else if (record.getSize() > iOutput.length() && !OGlobalConfiguration.RECORD_DOWNSIZING_ENABLED.getValueAsBoolean()) { + // APPEND EXTRA SPACES TO FILL ALL THE AVAILABLE SPACE AND AVOID FRAGMENTATION + newSize = record.getSize(); + } else if (overSize > 0) { + // APPEND EXTRA SPACES TO GET A LARGER iOutput + newSize = (int) (iOutput.length() * overSize); + } else + // NO OVERSIZE + newSize = iOutput.length(); + + if (newSize > iOutput.length()) { + iOutput.ensureCapacity(newSize); + for (int b = iOutput.length(); b < newSize; ++b) + iOutput.append(' '); + } + + return iOutput; + } + + private String getClassName(final Object iValue) { + if (iValue instanceof ODocument) + return ((ODocument) iValue).getClassName(); + + return iValue != null ? iValue.getClass().getSimpleName() : null; + } + + private OClass getLinkInfo(final ODatabaseInternal iDatabase, final String iFieldClassName) { + if (iDatabase == null || iDatabase.isClosed() || iFieldClassName == null) + return null; + + OClass linkedClass = ((OMetadataInternal) iDatabase.getMetadata()).getImmutableSchemaSnapshot().getClass(iFieldClassName); + + if (iDatabase.getDatabaseOwner() instanceof ODatabaseObject) { + ODatabaseObject dbo = (ODatabaseObject) iDatabase.getDatabaseOwner(); + if (linkedClass == null) { + Class entityClass = dbo.getEntityManager().getEntityClass(iFieldClassName); + if (entityClass != null) + // REGISTER IT + linkedClass = iDatabase.getMetadata().getSchema().createClass(iFieldClassName); + } + } + + return linkedClass; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerStringAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerStringAbstract.java new file mode 100755 index 00000000000..1d0fb1c752d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/record/string/ORecordSerializerStringAbstract.java @@ -0,0 +1,717 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.record.string; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OUserObject2RecordHandler; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OSchemaException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringSerializerAnyStreamable; +import com.orientechnologies.orient.core.serialization.serializer.string.OStringSerializerEmbedded; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.*; + +@SuppressWarnings("serial") +public abstract class ORecordSerializerStringAbstract implements ORecordSerializer, Serializable { + protected static final OProfiler PROFILER = Orient.instance().getProfiler(); + private static final char DECIMAL_SEPARATOR = '.'; + private static final String MAX_INTEGER_AS_STRING = String.valueOf(Integer.MAX_VALUE); + private static final int MAX_INTEGER_DIGITS = MAX_INTEGER_AS_STRING.length(); + + public static Object fieldTypeFromStream(final ODocument iDocument, OType iType, final Object iValue) { + if (iValue == null) + return null; + + if (iType == null) + iType = OType.EMBEDDED; + + switch (iType) { + case STRING: + case INTEGER: + case BOOLEAN: + case FLOAT: + case DECIMAL: + case LONG: + case DOUBLE: + case SHORT: + case BYTE: + case BINARY: + case DATE: + case DATETIME: + case LINK: + return simpleValueFromStream(iValue, iType); + + case EMBEDDED: { + // EMBEDED RECORD + final Object embeddedObject = OStringSerializerEmbedded.INSTANCE.fromStream((String) iValue); + if (embeddedObject instanceof ODocument) + ODocumentInternal.addOwner((ODocument) embeddedObject, iDocument); + + // EMBEDDED OBJECT + return embeddedObject; + } + + case CUSTOM: + // RECORD + final Object result = OStringSerializerAnyStreamable.INSTANCE.fromStream((String) iValue); + if (result instanceof ODocument) + ODocumentInternal.addOwner((ODocument) result, iDocument); + return result; + + case EMBEDDEDSET: + case EMBEDDEDLIST: { + final String value = (String) iValue; + return ORecordSerializerSchemaAware2CSV.INSTANCE.embeddedCollectionFromStream(iDocument, iType, null, null, value); + } + + case EMBEDDEDMAP: { + final String value = (String) iValue; + return ORecordSerializerSchemaAware2CSV.INSTANCE.embeddedMapFromStream(iDocument, null, value, null); + } + } + + throw new IllegalArgumentException("Type " + iType + " not supported to convert value: " + iValue); + } + + public static Object convertValue(final String iValue, final OType iExpectedType) { + final Object v = getTypeValue((String) iValue); + return OType.convert(v, iExpectedType.getDefaultJavaType()); + } + + public static void fieldTypeToString(final StringBuilder iBuffer, OType iType, final Object iValue) { + if (iValue == null) + return; + + final long timer = PROFILER.startChrono(); + + if (iType == null) { + if (iValue instanceof ORID) + iType = OType.LINK; + else + iType = OType.EMBEDDED; + } + + switch (iType) { + case STRING: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.string2string"), "Serialize string to string", timer); + break; + + case BOOLEAN: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.bool2string"), "Serialize boolean to string", timer); + break; + + case INTEGER: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.int2string"), "Serialize integer to string", timer); + break; + + case FLOAT: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.float2string"), "Serialize float to string", timer); + break; + + case DECIMAL: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.decimal2string"), "Serialize decimal to string", + timer); + break; + + case LONG: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.long2string"), "Serialize long to string", timer); + break; + + case DOUBLE: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.double2string"), "Serialize double to string", timer); + break; + + case SHORT: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.short2string"), "Serialize short to string", timer); + break; + + case BYTE: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.byte2string"), "Serialize byte to string", timer); + break; + + case BINARY: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.binary2string"), "Serialize binary to string", timer); + break; + + case DATE: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.date2string"), "Serialize date to string", timer); + break; + + case DATETIME: + simpleValueToStream(iBuffer, iType, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.datetime2string"), "Serialize datetime to string", + timer); + break; + + case LINK: + if (iValue instanceof ORecordId) + ((ORecordId) iValue).toString(iBuffer); + else + ((OIdentifiable) iValue).getIdentity().toString(iBuffer); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.link2string"), "Serialize link to string", timer); + break; + + case EMBEDDEDSET: + ORecordSerializerSchemaAware2CSV.INSTANCE + .embeddedCollectionToStream(ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(), null, iBuffer, null, null, iValue, true, + true); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedSet2string"), "Serialize embeddedset to string", + timer); + break; + + case EMBEDDEDLIST: + ORecordSerializerSchemaAware2CSV.INSTANCE + .embeddedCollectionToStream(ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(), null, iBuffer, null, null, iValue, true, + false); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedList2string"), + "Serialize embeddedlist to string", timer); + break; + + case EMBEDDEDMAP: + ORecordSerializerSchemaAware2CSV.INSTANCE.embeddedMapToStream(ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(), null, + iBuffer, null, null, iValue, true); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.embedMap2string"), "Serialize embeddedmap to string", + timer); + break; + + case EMBEDDED: + if (iValue instanceof ODocument) { + ORecordSerializerSchemaAware2CSV.INSTANCE.toString((ODocument) iValue, iBuffer, null); + } else + OStringSerializerEmbedded.INSTANCE.toStream(iBuffer, iValue); + PROFILER + .stopChrono(PROFILER.getProcessMetric("serializer.record.string.embed2string"), "Serialize embedded to string", timer); + break; + + case CUSTOM: + OStringSerializerAnyStreamable.INSTANCE.toStream(iBuffer, iValue); + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.custom2string"), "Serialize custom to string", timer); + break; + + default: + throw new IllegalArgumentException("Type " + iType + " not supported to convert value: " + iValue); + } + } + + /** + * Parses a string returning the closer type. Numbers by default are INTEGER if haven't decimal separator, otherwise FLOAT. To + * treat all the number types numbers are postponed with a character that tells the type: b=byte, s=short, l=long, f=float, + * d=double, t=date. + * + * @param iValue + * Value to parse + * @return The closest type recognized + */ + public static OType getType(final String iValue) { + if (iValue.length() == 0) + return null; + + final char firstChar = iValue.charAt(0); + + if (firstChar == ORID.PREFIX) + // RID + return OType.LINK; + else if (firstChar == '\'' || firstChar == '"') + return OType.STRING; + else if (firstChar == OStringSerializerHelper.BINARY_BEGINEND) + return OType.BINARY; + else if (firstChar == OStringSerializerHelper.EMBEDDED_BEGIN) + return OType.EMBEDDED; + else if (firstChar == OStringSerializerHelper.LIST_BEGIN) + return OType.EMBEDDEDLIST; + else if (firstChar == OStringSerializerHelper.SET_BEGIN) + return OType.EMBEDDEDSET; + else if (firstChar == OStringSerializerHelper.MAP_BEGIN) + return OType.EMBEDDEDMAP; + else if (firstChar == OStringSerializerHelper.CUSTOM_TYPE) + return OType.CUSTOM; + + // BOOLEAN? + if (iValue.equalsIgnoreCase("true") || iValue.equalsIgnoreCase("false")) + return OType.BOOLEAN; + + // NUMBER OR STRING? + boolean integer = true; + for (int index = 0; index < iValue.length(); ++index) { + final char c = iValue.charAt(index); + if (c < '0' || c > '9') + if ((index == 0 && (c == '+' || c == '-'))) + continue; + else if (c == DECIMAL_SEPARATOR) + integer = false; + else { + if (index > 0) + if (!integer && c == 'E') { + // CHECK FOR SCIENTIFIC NOTATION + if (index < iValue.length()) { + if (iValue.charAt(index + 1) == '-') + // JUMP THE DASH IF ANY (NOT MANDATORY) + index++; + continue; + } + } else if (c == 'f') + return index != (iValue.length() - 1) ? OType.STRING : OType.FLOAT; + else if (c == 'c') + return index != (iValue.length() - 1) ? OType.STRING : OType.DECIMAL; + else if (c == 'l') + return index != (iValue.length() - 1) ? OType.STRING : OType.LONG; + else if (c == 'd') + return index != (iValue.length() - 1) ? OType.STRING : OType.DOUBLE; + else if (c == 'b') + return index != (iValue.length() - 1) ? OType.STRING : OType.BYTE; + else if (c == 'a') + return index != (iValue.length() - 1) ? OType.STRING : OType.DATE; + else if (c == 't') + return index != (iValue.length() - 1) ? OType.STRING : OType.DATETIME; + else if (c == 's') + return index != (iValue.length() - 1) ? OType.STRING : OType.SHORT; + else if (c == 'e') { //eg. 1e-06 + try{ + Double.parseDouble(iValue); + return OType.DOUBLE; + }catch (Exception e){ + return OType.STRING; + } + } + + return OType.STRING; + } + } + + if (integer) { + // AUTO CONVERT TO LONG IF THE INTEGER IS TOO BIG + final int numberLength = iValue.length(); + if (numberLength > MAX_INTEGER_DIGITS || (numberLength == MAX_INTEGER_DIGITS && iValue.compareTo(MAX_INTEGER_AS_STRING) > 0)) + return OType.LONG; + + return OType.INTEGER; + } + + // CHECK IF THE DECIMAL NUMBER IS A FLOAT OR DOUBLE + final double dou = Double.parseDouble(iValue); + if (dou <= Float.MAX_VALUE && dou >= Float.MIN_VALUE && Double.toString(dou).equals(Float.toString((float) dou)) + && new Double(new Double(dou).floatValue()).doubleValue() == dou) { + return OType.FLOAT; + } else if (!new Double(dou).toString().equals(iValue)) { + return OType.DECIMAL; + } + + return OType.DOUBLE; + } + + /** + * Parses the field type char returning the closer type. Default is STRING. b=binary if iValue.lenght() >= 4 b=byte if + * iValue.lenght() <= 3 s=short, l=long f=float d=double a=date t=datetime + * + * @param iValue + * Value to parse + * @param iCharType + * Char value indicating the type + * @return The closest type recognized + */ + public static OType getType(final String iValue, final char iCharType) { + if (iCharType == 'f') + return OType.FLOAT; + else if (iCharType == 'c') + return OType.DECIMAL; + else if (iCharType == 'l') + return OType.LONG; + else if (iCharType == 'd') + return OType.DOUBLE; + else if (iCharType == 'b') { + if (iValue.length() >= 1 && iValue.length() <= 3) + return OType.BYTE; + else + return OType.BINARY; + } else if (iCharType == 'a') + return OType.DATE; + else if (iCharType == 't') + return OType.DATETIME; + else if (iCharType == 's') + return OType.SHORT; + else if (iCharType == 'e') + return OType.EMBEDDEDSET; + else if (iCharType == 'g') + return OType.LINKBAG; + else if (iCharType == 'z') + return OType.LINKLIST; + else if (iCharType == 'm') + return OType.LINKMAP; + else if (iCharType == 'x') + return OType.LINK; + else if (iCharType == 'n') + return OType.LINKSET; + else if (iCharType == 'x') + return OType.LINK; + else if (iCharType == 'u') + return OType.CUSTOM; + + return OType.STRING; + } + + /** + * Parses a string returning the value with the closer type. Numbers by default are INTEGER if haven't decimal separator, + * otherwise FLOAT. To treat all the number types numbers are postponed with a character that tells the type: b=byte, s=short, + * l=long, f=float, d=double, t=date. If starts with # it's a RecordID. Most of the code is equals to getType() but has been + * copied to speed-up it. + * + * @param iValue + * Value to parse + * @return The closest type recognized + */ + public static Object getTypeValue(final String iValue) { + if (iValue == null || iValue.equalsIgnoreCase("NULL")) + return null; + + if (iValue.length() == 0) + return ""; + + if (iValue.length() > 1) + if (iValue.charAt(0) == '"' && iValue.charAt(iValue.length() - 1) == '"') + // STRING + return OStringSerializerHelper.decode(iValue.substring(1, iValue.length() - 1)); + else if (iValue.charAt(0) == OStringSerializerHelper.BINARY_BEGINEND + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.BINARY_BEGINEND) + // STRING + return OStringSerializerHelper.getBinaryContent(iValue); + else if (iValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.LIST_END) { + // LIST + final ArrayList coll = new ArrayList(); + OStringSerializerHelper.getCollection(iValue, 0, coll, OStringSerializerHelper.LIST_BEGIN, + OStringSerializerHelper.LIST_END, OStringSerializerHelper.COLLECTION_SEPARATOR); + return coll; + } else if (iValue.charAt(0) == OStringSerializerHelper.SET_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.SET_END) { + // SET + final Set coll = new HashSet(); + OStringSerializerHelper.getCollection(iValue, 0, coll, OStringSerializerHelper.SET_BEGIN, OStringSerializerHelper.SET_END, + OStringSerializerHelper.COLLECTION_SEPARATOR); + return coll; + } else if (iValue.charAt(0) == OStringSerializerHelper.MAP_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.MAP_END) { + // MAP + return OStringSerializerHelper.getMap(iValue); + } + + if (iValue.charAt(0) == ORID.PREFIX) + // RID + return new ORecordId(iValue); + + boolean integer = true; + char c; + + boolean stringStarBySign = false; + + for (int index = 0; index < iValue.length(); ++index) { + c = iValue.charAt(index); + if (c < '0' || c > '9') { + if ((index == 0 && (c == '+' || c == '-'))) { + stringStarBySign = true; + continue; + } else if (c == DECIMAL_SEPARATOR) + integer = false; + else { + if (index > 0) { + if (!integer && c == 'E') { + // CHECK FOR SCIENTIFIC NOTATION + if (index < iValue.length()) + index++; + if (iValue.charAt(index) == '-') + continue; + } + + final String v = iValue.substring(0, index); + + if (c == 'f') + return new Float(v); + else if (c == 'c') + return new BigDecimal(v); + else if (c == 'l') + return new Long(v); + else if (c == 'd') + return new Double(v); + else if (c == 'b') + return new Byte(v); + else if (c == 'a' || c == 't') + return new Date(Long.parseLong(v)); + else if (c == 's') + return new Short(v); + } + return iValue; + } + } else if (stringStarBySign) { + stringStarBySign = false; + } + } + if (stringStarBySign) + return iValue; + + if (integer) { + try { + return new Integer(iValue); + } catch (NumberFormatException e) { + return new Long(iValue); + } + } else if ("NaN".equals(iValue) || "Infinity".equals(iValue)) + // NaN and Infinity CANNOT BE MANAGED BY BIG-DECIMAL TYPE + return new Double(iValue); + else + return new BigDecimal(iValue); + } + + public static Object simpleValueFromStream(final Object iValue, final OType iType) { + switch (iType) { + case STRING: + if (iValue instanceof String) { + final String s = OIOUtils.getStringContent(iValue); + return OStringSerializerHelper.decode(s); + } + return iValue.toString(); + + case INTEGER: + if (iValue instanceof Integer) + return iValue; + return new Integer(iValue.toString()); + + case BOOLEAN: + if (iValue instanceof Boolean) + return iValue; + return new Boolean(iValue.toString()); + + case FLOAT: + if (iValue instanceof Float) + return iValue; + return convertValue((String) iValue, iType); + + case DECIMAL: + if (iValue instanceof BigDecimal) + return iValue; + return convertValue((String) iValue, iType); + + case LONG: + if (iValue instanceof Long) + return iValue; + return convertValue((String) iValue, iType); + + case DOUBLE: + if (iValue instanceof Double) + return iValue; + return convertValue((String) iValue, iType); + + case SHORT: + if (iValue instanceof Short) + return iValue; + return convertValue((String) iValue, iType); + + case BYTE: + if (iValue instanceof Byte) + return iValue; + return convertValue((String) iValue, iType); + + case BINARY: + return OStringSerializerHelper.getBinaryContent(iValue); + + case DATE: + case DATETIME: + if (iValue instanceof Date) + return iValue; + return convertValue((String) iValue, iType); + + case LINK: + if (iValue instanceof ORID) + return iValue.toString(); + else if (iValue instanceof String) + return new ORecordId((String) iValue); + else + return ((ORecord) iValue).getIdentity().toString(); + } + + throw new IllegalArgumentException("Type " + iType + " is not simple type."); + } + + public static void simpleValueToStream(final StringBuilder iBuffer, final OType iType, final Object iValue) { + if (iValue == null || iType == null) + return; + switch (iType) { + case STRING: + iBuffer.append('"'); + iBuffer.append(OStringSerializerHelper.encode(iValue.toString())); + iBuffer.append('"'); + break; + + case BOOLEAN: + iBuffer.append(String.valueOf(iValue)); + break; + + case INTEGER: + iBuffer.append(String.valueOf(iValue)); + break; + + case FLOAT: + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('f'); + break; + + case DECIMAL: + if (iValue instanceof BigDecimal) + iBuffer.append(((BigDecimal) iValue).toPlainString()); + else + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('c'); + break; + + case LONG: + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('l'); + break; + + case DOUBLE: + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('d'); + break; + + case SHORT: + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('s'); + break; + + case BYTE: + if (iValue instanceof Character) + iBuffer.append((int) ((Character) iValue).charValue()); + else if (iValue instanceof String) + iBuffer.append(String.valueOf((int) ((String) iValue).charAt(0))); + else + iBuffer.append(String.valueOf(iValue)); + iBuffer.append('b'); + break; + + case BINARY: + iBuffer.append(OStringSerializerHelper.BINARY_BEGINEND); + if (iValue instanceof Byte) + iBuffer.append(OBase64Utils.encodeBytes(new byte[] { ((Byte) iValue).byteValue() })); + else + iBuffer.append(OBase64Utils.encodeBytes((byte[]) iValue)); + iBuffer.append(OStringSerializerHelper.BINARY_BEGINEND); + break; + + case DATE: + if (iValue instanceof Date) { + // RESET HOURS, MINUTES, SECONDS AND MILLISECONDS + final Calendar calendar = ODateHelper.getDatabaseCalendar(); + calendar.setTime((Date) iValue); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + iBuffer.append(calendar.getTimeInMillis()); + } else + iBuffer.append(iValue); + iBuffer.append('a'); + break; + + case DATETIME: + if (iValue instanceof Date) + iBuffer.append(((Date) iValue).getTime()); + else + iBuffer.append(iValue); + iBuffer.append('t'); + break; + } + } + + public abstract ORecord fromString(String iContent, ORecord iRecord, String[] iFields); + + public StringBuilder toString(final ORecord iRecord, final StringBuilder iOutput, final String iFormat) { + return toString(iRecord, iOutput, iFormat, null, false, true); + } + + public ORecord fromString(final String iSource) { + return fromString(iSource, (ORecord) ODatabaseRecordThreadLocal.INSTANCE.get().newInstance(), null); + } + + @Override + public String[] getFieldNames(ODocument reference, byte[] iSource) { + return null; + } + + public ORecord fromStream(final byte[] iSource, final ORecord iRecord, final String[] iFields) { + final long timer = PROFILER.startChrono(); + + try { + return fromString(new String(iSource,"UTF-8"), iRecord, iFields); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSchemaException("Error reading record"),e); + } finally { + + PROFILER + .stopChrono(PROFILER.getProcessMetric("serializer.record.string.fromStream"), "Deserialize record from stream", timer); + } + } + + public byte[] toStream(final ORecord iRecord, boolean iOnlyDelta) { + final long timer = PROFILER.startChrono(); + + try { + return toString(iRecord, new StringBuilder(2048), null, null, iOnlyDelta, true).toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSchemaException("error encoding string"), e); + } finally { + + PROFILER.stopChrono(PROFILER.getProcessMetric("serializer.record.string.toStream"), "Serialize record to stream", timer); + } + } + + protected abstract StringBuilder toString(final ORecord iRecord, final StringBuilder iOutput, final String iFormat, + final OUserObject2RecordHandler iObjHandler, boolean iOnlyDelta, boolean autoDetectCollectionType); + + public boolean getSupportBinaryEvaluate() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializer.java new file mode 100644 index 00000000000..19269bd3951 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializer.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +public interface OStreamSerializer { + byte[] toStream(Object iObject) throws IOException; + + Object fromStream(byte[] iStream) throws IOException; + + String getName(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyRecord.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyRecord.java new file mode 100755 index 00000000000..76181154457 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyRecord.java @@ -0,0 +1,95 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +import java.io.IOException; +import java.lang.reflect.Constructor; + +/** + * Allow short and long form. Examples:
          + * Short form: @[type][RID] where type = 1 byte
          + * Long form: org.myapp.Myrecord|[RID]
          + * + * @author Luca Garulli + * + */ +public class OStreamSerializerAnyRecord implements OStreamSerializer { + public static final String NAME = "ar"; + public static final OStreamSerializerAnyRecord INSTANCE = new OStreamSerializerAnyRecord(); + + /** + * Re-Create any object if the class has a public constructor that accepts a String as unique parameter. + */ + public Object fromStream(byte[] iStream) throws IOException { + if (iStream == null || iStream.length == 0) + // NULL VALUE + return null; + + final String stream = new String(iStream,"UTF-8"); + + Class cls = null; + + try { + final StringBuilder content = new StringBuilder(1024); + cls = OStreamSerializerHelper.readRecordType(stream, content); + + // TRY WITH THE DATABASE CONSTRUCTOR + for (Constructor c : cls.getDeclaredConstructors()) { + Class[] params = c.getParameterTypes(); + + if (params.length == 2 && params[1].equals(ORID.class)) { + ORecord rec = (ORecord) c.newInstance(new ORecordId(content.toString())); + // rec.load(); + return rec; + } + } + } catch (Exception e) { + throw OException.wrapException( + new OSerializationException("Error on unmarshalling content. Class " + (cls != null ? cls.getName() : "?")), e); + } + + throw new OSerializationException("Cannot unmarshall the record since the serialized object of class " + + (cls != null ? cls.getSimpleName() : "?") + " has no constructor with suitable parameters: (ORID)"); + } + + public byte[] toStream(Object iObject) throws IOException { + if (iObject == null) + return null; + + if (((ORecord) iObject).getIdentity() == null) + throw new OSerializationException("Cannot serialize record without identity. Store it before serialization."); + + final StringBuilder buffer = OStreamSerializerHelper.writeRecordType(iObject.getClass(), new StringBuilder(1024)); + buffer.append(((ORecord) iObject).getIdentity().toString()); + + return buffer.toString().getBytes("UTF-8"); + } + + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyStreamable.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyStreamable.java new file mode 100755 index 00000000000..15d1024f151 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerAnyStreamable.java @@ -0,0 +1,138 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; +import java.util.Arrays; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OArrays; +import com.orientechnologies.orient.core.command.script.OCommandScript; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.query.OQuery; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.query.OLiveQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +public class OStreamSerializerAnyStreamable implements OStreamSerializer { + private static final String SCRIPT_COMMAND_CLASS = "s"; + private static final byte[] SCRIPT_COMMAND_CLASS_ASBYTES = SCRIPT_COMMAND_CLASS.getBytes(); + private static final String SQL_COMMAND_CLASS = "c"; + private static final byte[] SQL_COMMAND_CLASS_ASBYTES = SQL_COMMAND_CLASS.getBytes(); + private static final String QUERY_COMMAND_CLASS = "q"; + private static final byte[] QUERY_COMMAND_CLASS_ASBYTES = QUERY_COMMAND_CLASS.getBytes(); + + public static final OStreamSerializerAnyStreamable INSTANCE = new OStreamSerializerAnyStreamable(); + public static final String NAME = "at"; + + /** + * Re-Create any object if the class has a public constructor that accepts a String as unique parameter. + */ + public Object fromStream(final byte[] iStream) throws IOException { + if (iStream == null || iStream.length == 0) + // NULL VALUE + return null; + + final int classNameSize = OBinaryProtocol.bytes2int(iStream); + + if (classNameSize <= 0) { + final String message = "Class signature not found in ANY element: " + Arrays.toString(iStream); + OLogManager.instance().error(this, message); + + throw new OSerializationException(message); + } + + + final String className = new String(iStream,4,classNameSize,"UTF-8"); + + try { + final OSerializableStream stream; + // CHECK FOR ALIASES + if (className.equalsIgnoreCase("q")) + // QUERY + stream = new OSQLSynchQuery(); + else if (className.equalsIgnoreCase("c")) + // SQL COMMAND + stream = new OCommandSQL(); + else if (className.equalsIgnoreCase("s")) + // SCRIPT COMMAND + stream = new OCommandScript(); + else + // CREATE THE OBJECT BY INVOKING THE EMPTY CONSTRUCTOR + stream = (OSerializableStream) Class.forName(className).newInstance(); + + return stream.fromStream(OArrays.copyOfRange(iStream, 4 + classNameSize, iStream.length)); + + } catch (Exception e) { + final String message = "Error on unmarshalling content. Class: " + className; + OLogManager.instance().error(this, message, e); + throw OException.wrapException(new OSerializationException(message), e); + } + } + + /** + * Serialize the class name size + class name + object content + */ + public byte[] toStream(final Object iObject) throws IOException { + if (iObject == null) + return null; + + if (!(iObject instanceof OSerializableStream)) + throw new OSerializationException("Cannot serialize the object [" + iObject.getClass() + ":" + iObject + + "] since it does not implement the OSerializableStream interface"); + + OSerializableStream stream = (OSerializableStream) iObject; + + // SERIALIZE THE CLASS NAME + final byte[] className; + if (iObject instanceof OLiveQuery) + className = iObject.getClass().getName().getBytes("UTF-8"); + else if (iObject instanceof OSQLSynchQuery) + className = QUERY_COMMAND_CLASS_ASBYTES; + else if (iObject instanceof OCommandSQL) + className = SQL_COMMAND_CLASS_ASBYTES; + else if (iObject instanceof OCommandScript) + className = SCRIPT_COMMAND_CLASS_ASBYTES; + else { + if (iObject == null) + className = null; + else + className = iObject.getClass().getName().getBytes("UTF-8"); + } + // SERIALIZE THE OBJECT CONTENT + byte[] objectContent = stream.toStream(); + + byte[] result = new byte[4 + className.length + objectContent.length]; + + // COPY THE CLASS NAME SIZE + CLASS NAME + OBJECT CONTENT + System.arraycopy(OBinaryProtocol.int2bytes(className.length), 0, result, 0, 4); + System.arraycopy(className, 0, result, 4, className.length); + System.arraycopy(objectContent, 0, result, 4 + className.length, objectContent.length); + + return result; + } + + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerHelper.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerHelper.java new file mode 100755 index 00000000000..4d4442a395b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerHelper.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.entity.OClassDictionary; +import com.orientechnologies.orient.core.exception.OSerializationException; + +/** + * Abstract class. Allows short and long form. Examples:
          + * Short form: @[type][RID] where type = 1 byte
          + * Long form: org.myapp.Myrecord|[RID]
          + * + * @author Luca Garulli + * + */ +public class OStreamSerializerHelper { + + public static final String SEPARATOR = "|"; + private static final char SHORT_FORM_PREFIX = '!'; + + public static StringBuilder writeRecordType(final Class cls, final StringBuilder iBuffer) { + // SEARCH INTO THE SERIALIZER REGISTER IF THE IMPLEMENTATION WAS REGISTERED TO GET THE SHORT FORM (AND OPTIMIZING IN SIZE AND + // WRITE TIMES) + Character c = OClassDictionary.instance().getCodeByClass(cls); + if (c != null) { + // FOUND: WRITE THE SHORT FORM + iBuffer.append(SHORT_FORM_PREFIX); + iBuffer.append(c); + } else { + // NOT FOUND: PROBABLY A CUSTOM IMPL: WRITE THE FULL CLASS NAME + iBuffer.append(cls.getName()); + iBuffer.append(SEPARATOR); + } + return iBuffer; + } + + public static Class readRecordType(final String iBuffer, final StringBuilder iContent) throws ClassNotFoundException { + Class cls; + final int pos; + if (iBuffer.charAt(0) == SHORT_FORM_PREFIX) { + // SHORT FORM + cls = OClassDictionary.instance().getClassByCode(iBuffer.charAt(1)); + pos = 1; + } else { + // LONG FORM + pos = iBuffer.indexOf(SEPARATOR); + if (pos < 0) { + final String message = "Class signature not found in the buffer: " + iBuffer; + OLogManager.instance().error(null, message); + + throw new OSerializationException(message); + } + + final String className = iBuffer.substring(0, pos); + cls = Class.forName(className); + } + + // SET THE CONTENT + iContent.append(iBuffer.substring(pos + 1)); + + return cls; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerInteger.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerInteger.java new file mode 100644 index 00000000000..f13b08b95c9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerInteger.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +public class OStreamSerializerInteger implements OStreamSerializer { + public static final String NAME = "i"; + + public static final OStreamSerializerInteger INSTANCE = new OStreamSerializerInteger(); + + public String getName() { + return NAME; + } + + public Object fromStream(final byte[] iStream) throws IOException { + return OBinaryProtocol.bytes2int(iStream); + } + + public byte[] toStream(final Object iObject) throws IOException { + return OBinaryProtocol.int2bytes((Integer) iObject); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLiteral.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLiteral.java new file mode 100644 index 00000000000..e53cc228a91 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLiteral.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerStringAbstract; + +public class OStreamSerializerLiteral implements OStreamSerializer { + public static final String NAME = "li"; + + public static final OStreamSerializerLiteral INSTANCE = new OStreamSerializerLiteral(); + + public String getName() { + return NAME; + } + + public Object fromStream(final byte[] iStream) throws IOException { + return ORecordSerializerStringAbstract.getTypeValue(new String(iStream,"UTF-8")); + } + + public byte[] toStream(final Object iObject) throws IOException { + if (iObject == null) + return null; + + final StringBuilder buffer = new StringBuilder(); + ORecordSerializerStringAbstract.fieldTypeToString(buffer, OType.getTypeByClass(iObject.getClass()), iObject); + return buffer.toString().getBytes("UTF-8"); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLong.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLong.java new file mode 100644 index 00000000000..460ddd39ca6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerLong.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +public class OStreamSerializerLong implements OStreamSerializer { + public static final String NAME = "lo"; + + public static final OStreamSerializerLong INSTANCE = new OStreamSerializerLong(); + + public String getName() { + return NAME; + } + + public Object fromStream(final byte[] iStream) throws IOException { + return OBinaryProtocol.bytes2long(iStream); + } + + public byte[] toStream(final Object iObject) throws IOException { + return OBinaryProtocol.long2bytes((Long) iObject); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRID.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRID.java new file mode 100644 index 00000000000..2f4670b40bb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRID.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class OStreamSerializerRID implements OStreamSerializer, OBinarySerializer { + public static final String NAME = "p"; + public static final OStreamSerializerRID INSTANCE = new OStreamSerializerRID(); + public static final byte ID = 16; + + public String getName() { + return NAME; + } + + public Object fromStream(final byte[] iStream) throws IOException { + if (iStream == null) + return null; + + return new ORecordId().fromStream(iStream); + } + + public byte[] toStream(final Object iObject) throws IOException { + if (iObject == null) + return null; + + return ((OIdentifiable) iObject).getIdentity().toStream(); + } + + public int getObjectSize(OIdentifiable object, Object... hints) { + return OLinkSerializer.INSTANCE.getObjectSize(object.getIdentity()); + } + + public void serialize(OIdentifiable object, byte[] stream, int startPosition, Object... hints) { + OLinkSerializer.INSTANCE.serialize(object.getIdentity(), stream, startPosition); + } + + public ORID deserialize(byte[] stream, int startPosition) { + return OLinkSerializer.INSTANCE.deserialize(stream, startPosition); + } + + public int getObjectSize(byte[] stream, int startPosition) { + return OLinkSerializer.INSTANCE.getObjectSize(stream, startPosition); + } + + public byte getId() { + return ID; + } + + public int getObjectSizeNative(byte[] stream, int startPosition) { + return OLinkSerializer.INSTANCE.getObjectSizeNative(stream, startPosition); + } + + public void serializeNativeObject(OIdentifiable object, byte[] stream, int startPosition, Object... hints) { + OLinkSerializer.INSTANCE.serializeNativeObject(object.getIdentity(), stream, startPosition); + } + + public OIdentifiable deserializeNativeObject(byte[] stream, int startPosition) { + return OLinkSerializer.INSTANCE.deserializeNativeObject(stream, startPosition); + } + + public boolean isFixedLength() { + return true; + } + + public int getFixedLength() { + return OLinkSerializer.RID_SIZE; + } + + @Override + public OIdentifiable preprocess(OIdentifiable value, Object... hints) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(OIdentifiable object, ByteBuffer buffer, Object... hints) { + OLinkSerializer.INSTANCE.serializeInByteBufferObject(object, buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public OIdentifiable deserializeFromByteBufferObject(ByteBuffer buffer) { + return OLinkSerializer.INSTANCE.deserializeFromByteBufferObject(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + return OLinkSerializer.INSTANCE.getObjectSizeInByteBuffer(buffer); + } + + /** + * {@inheritDoc} + */ + @Override + public OIdentifiable deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OLinkSerializer.INSTANCE.deserializeFromByteBufferObject(buffer, walChanges, offset); + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + return OLinkSerializer.INSTANCE.getObjectSizeInByteBuffer(buffer, walChanges, offset); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRecord.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRecord.java new file mode 100644 index 00000000000..00c4e0fa34a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerRecord.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; + +public class OStreamSerializerRecord implements OStreamSerializer { + public static final String NAME = "l"; + public static final OStreamSerializerRecord INSTANCE = new OStreamSerializerRecord(); + + public String getName() { + return NAME; + } + + /** + * Re-Create any object if the class has a public constructor that accepts a String as unique parameter. + */ + public Object fromStream(final byte[] iStream) throws IOException { + if (iStream == null || iStream.length == 0) + // NULL VALUE + return null; + + final ORecord obj = Orient.instance().getRecordFactoryManager().newInstance(); + + final ORID rid = new ORecordId().fromStream(iStream); + + ORecordInternal.setIdentity(obj, rid.getClusterId(), rid.getClusterPosition()); + return obj; + } + + public byte[] toStream(final Object iObject) throws IOException { + if (iObject == null) + return null; + + if (((ORecord) iObject).getIdentity() == null) + throw new OSerializationException("Cannot serialize record without identity. Store it before to serialize."); + + return ((ORecord) iObject).getIdentity().toStream(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerSBTreeIndexRIDContainer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerSBTreeIndexRIDContainer.java new file mode 100644 index 00000000000..449a20e390a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerSBTreeIndexRIDContainer.java @@ -0,0 +1,301 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.serialization.types.OBooleanSerializer; +import com.orientechnologies.common.serialization.types.OIntegerSerializer; +import com.orientechnologies.common.serialization.types.OLongSerializer; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainer; +import com.orientechnologies.orient.core.db.record.ridbag.sbtree.OIndexRIDContainerSBTree; +import com.orientechnologies.orient.core.index.sbtreebonsai.local.OBonsaiBucketPointer; +import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; + +import static com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer.RID_SIZE; + +public class OStreamSerializerSBTreeIndexRIDContainer implements OStreamSerializer, OBinarySerializer { + public static final String NAME = "icn"; + public static final OStreamSerializerSBTreeIndexRIDContainer INSTANCE = new OStreamSerializerSBTreeIndexRIDContainer(); + + public static final byte ID = 21; + public static final int FILE_ID_OFFSET = 0; + public static final int EMBEDDED_OFFSET = FILE_ID_OFFSET + OLongSerializer.LONG_SIZE; + public static final int DURABLE_OFFSET = EMBEDDED_OFFSET + OBooleanSerializer.BOOLEAN_SIZE; + public static final int SBTREE_ROOTINDEX_OFFSET = DURABLE_OFFSET + OBooleanSerializer.BOOLEAN_SIZE; + public static final int SBTREE_ROOTOFFSET_OFFSET = SBTREE_ROOTINDEX_OFFSET + OLongSerializer.LONG_SIZE; + + public static final int EMBEDDED_SIZE_OFFSET = DURABLE_OFFSET + OBooleanSerializer.BOOLEAN_SIZE; + public static final int EMBEDDED_VALUES_OFFSET = EMBEDDED_SIZE_OFFSET + OIntegerSerializer.INT_SIZE; + + public static final OLongSerializer LONG_SERIALIZER = OLongSerializer.INSTANCE; + public static final OBooleanSerializer BOOLEAN_SERIALIZER = OBooleanSerializer.INSTANCE; + public static final OIntegerSerializer INT_SERIALIZER = OIntegerSerializer.INSTANCE; + public static final int SBTREE_CONTAINER_SIZE = + 2 * OBooleanSerializer.BOOLEAN_SIZE + 2 * OLongSerializer.LONG_SIZE + OIntegerSerializer.INT_SIZE; + public static final OLinkSerializer LINK_SERIALIZER = OLinkSerializer.INSTANCE; + + public Object fromStream(final byte[] iStream) throws IOException { + if (iStream == null) + return null; + + throw new UnsupportedOperationException("not implemented yet"); + } + + public byte[] toStream(final Object iObject) throws IOException { + if (iObject == null) + return null; + + throw new UnsupportedOperationException("not implemented yet"); + } + + public String getName() { + return NAME; + } + + @Override + public int getObjectSize(OIndexRIDContainer object, Object... hints) { + if (object.isEmbedded()) { + return embeddedObjectSerializedSize(object.size()); + } else { + return SBTREE_CONTAINER_SIZE; + } + } + + @Override + public int getObjectSize(byte[] stream, int startPosition) { + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public void serialize(OIndexRIDContainer object, byte[] stream, int startPosition, Object... hints) { + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public OIndexRIDContainer deserialize(byte[] stream, int startPosition) { + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public byte getId() { + return ID; + } + + @Override + public boolean isFixedLength() { + return false; + } + + @Override + public int getFixedLength() { + throw new UnsupportedOperationException("Length is not fixed"); + } + + @Override + public void serializeNativeObject(OIndexRIDContainer object, byte[] stream, int offset, Object... hints) { + LONG_SERIALIZER.serializeNative(object.getFileId(), stream, offset + FILE_ID_OFFSET); + + final boolean embedded = object.isEmbedded(); + final boolean durable = object.isDurableNonTxMode(); + + BOOLEAN_SERIALIZER.serializeNative(embedded, stream, offset + EMBEDDED_OFFSET); + BOOLEAN_SERIALIZER.serializeNative(durable, stream, offset + DURABLE_OFFSET); + + if (embedded) { + INT_SERIALIZER.serializeNative(object.size(), stream, offset + EMBEDDED_SIZE_OFFSET); + + int p = offset + EMBEDDED_VALUES_OFFSET; + for (OIdentifiable ids : object) { + LINK_SERIALIZER.serializeNativeObject(ids, stream, p); + p += RID_SIZE; + } + } else { + final OIndexRIDContainerSBTree underlying = (OIndexRIDContainerSBTree) object.getUnderlying(); + final OBonsaiBucketPointer rootPointer = underlying.getRootPointer(); + LONG_SERIALIZER.serializeNative(rootPointer.getPageIndex(), stream, offset + SBTREE_ROOTINDEX_OFFSET); + INT_SERIALIZER.serializeNative(rootPointer.getPageOffset(), stream, offset + SBTREE_ROOTOFFSET_OFFSET); + } + } + + @Override + public OIndexRIDContainer deserializeNativeObject(byte[] stream, int offset) { + final long fileId = LONG_SERIALIZER.deserializeNative(stream, offset + FILE_ID_OFFSET); + final boolean durable = BOOLEAN_SERIALIZER.deserializeNative(stream, offset + DURABLE_OFFSET); + + if (BOOLEAN_SERIALIZER.deserializeNative(stream, offset + EMBEDDED_OFFSET)) { + final int size = INT_SERIALIZER.deserializeNative(stream, offset + EMBEDDED_SIZE_OFFSET); + final Set underlying = new HashSet(Math.max((int) (size / .75f) + 1, 16)); + + int p = offset + EMBEDDED_VALUES_OFFSET; + for (int i = 0; i < size; i++) { + underlying.add(LINK_SERIALIZER.deserializeNativeObject(stream, p)); + p += RID_SIZE; + } + + return new OIndexRIDContainer(fileId, underlying, durable); + } else { + final long pageIndex = LONG_SERIALIZER.deserializeNative(stream, offset + SBTREE_ROOTINDEX_OFFSET); + final int pageOffset = INT_SERIALIZER.deserializeNative(stream, offset + SBTREE_ROOTOFFSET_OFFSET); + final OBonsaiBucketPointer rootPointer = new OBonsaiBucketPointer(pageIndex, pageOffset); + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OIndexRIDContainerSBTree underlying = new OIndexRIDContainerSBTree(fileId, rootPointer, durable, + (OAbstractPaginatedStorage) db.getStorage().getUnderlying()); + return new OIndexRIDContainer(fileId, underlying, durable); + } + } + + @Override + public int getObjectSizeNative(byte[] stream, int startPosition) { + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public OIndexRIDContainer preprocess(OIndexRIDContainer value, Object... hints) { + return value; + } + + private int embeddedObjectSerializedSize(int size) { + return OLongSerializer.LONG_SIZE + 2 * OBooleanSerializer.BOOLEAN_SIZE + OIntegerSerializer.INT_SIZE + size * RID_SIZE; + } + + /** + * {@inheritDoc} + */ + @Override + public void serializeInByteBufferObject(OIndexRIDContainer object, ByteBuffer buffer, Object... hints) { + buffer.putLong(object.getFileId()); + + final boolean embedded = object.isEmbedded(); + final boolean durable = object.isDurableNonTxMode(); + + buffer.put((byte) (embedded ? 1 : 0)); + buffer.put((byte) (durable ? 1 : 0)); + + if (embedded) { + buffer.putInt(object.size()); + + for (OIdentifiable ids : object) { + LINK_SERIALIZER.serializeInByteBufferObject(ids, buffer); + } + } else { + final OIndexRIDContainerSBTree underlying = (OIndexRIDContainerSBTree) object.getUnderlying(); + final OBonsaiBucketPointer rootPointer = underlying.getRootPointer(); + + buffer.putLong(rootPointer.getPageIndex()); + buffer.putInt(rootPointer.getPageOffset()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public OIndexRIDContainer deserializeFromByteBufferObject(ByteBuffer buffer) { + final long fileId = buffer.getLong(); + final boolean embedded = buffer.get() > 0; + final boolean durable = buffer.get() > 0; + + if (embedded) { + final int size = buffer.getInt(); + final Set underlying = new HashSet(Math.max((int) (size / .75f) + 1, 16)); + + for (int i = 0; i < size; i++) { + underlying.add(LINK_SERIALIZER.deserializeFromByteBufferObject(buffer)); + } + + return new OIndexRIDContainer(fileId, underlying, durable); + } else { + final long pageIndex = buffer.getLong(); + final int pageOffset = buffer.getInt(); + + final OBonsaiBucketPointer rootPointer = new OBonsaiBucketPointer(pageIndex, pageOffset); + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OIndexRIDContainerSBTree underlying = new OIndexRIDContainerSBTree(fileId, rootPointer, durable, + (OAbstractPaginatedStorage) db.getStorage().getUnderlying()); + return new OIndexRIDContainer(fileId, underlying, durable); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer) { + final int offset = buffer.position(); + buffer.position(); + if (buffer.get(offset + EMBEDDED_OFFSET) > 0) { + return embeddedObjectSerializedSize(buffer.getInt(offset + EMBEDDED_SIZE_OFFSET)); + } else { + return SBTREE_CONTAINER_SIZE; + } + } + + /** + * {@inheritDoc} + */ + @Override + public OIndexRIDContainer deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) { + final long fileId = walChanges.getLongValue(buffer, offset + FILE_ID_OFFSET); + final boolean durable = walChanges.getByteValue(buffer, offset + DURABLE_OFFSET) > 0; + + if (walChanges.getByteValue(buffer, offset + EMBEDDED_OFFSET) > 0) { + final int size = walChanges.getIntValue(buffer, offset + EMBEDDED_SIZE_OFFSET); + final Set underlying = new HashSet(Math.max((int) (size / .75f) + 1, 16)); + + int p = offset + EMBEDDED_VALUES_OFFSET; + for (int i = 0; i < size; i++) { + underlying.add(LINK_SERIALIZER.deserializeFromByteBufferObject(buffer, walChanges, p)); + p += RID_SIZE; + } + + return new OIndexRIDContainer(fileId, underlying, durable); + } else { + final long pageIndex = walChanges.getLongValue(buffer, offset + SBTREE_ROOTINDEX_OFFSET); + final int pageOffset = walChanges.getIntValue(buffer, offset + SBTREE_ROOTOFFSET_OFFSET); + final OBonsaiBucketPointer rootPointer = new OBonsaiBucketPointer(pageIndex, pageOffset); + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get(); + final OIndexRIDContainerSBTree underlying = new OIndexRIDContainerSBTree(fileId, rootPointer, durable, + (OAbstractPaginatedStorage) db.getStorage().getUnderlying()); + return new OIndexRIDContainer(fileId, underlying, durable); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) { + if (walChanges.getByteValue(buffer, offset + EMBEDDED_OFFSET) > 0) { + return embeddedObjectSerializedSize(walChanges.getIntValue(buffer, offset + EMBEDDED_SIZE_OFFSET)); + } else { + return SBTREE_CONTAINER_SIZE; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerString.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerString.java new file mode 100644 index 00000000000..3905fba74ef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/stream/OStreamSerializerString.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.stream; + +import java.io.IOException; + +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; + +public class OStreamSerializerString implements OStreamSerializer { + public static final String NAME = "s"; + + public static final OStreamSerializerString INSTANCE = new OStreamSerializerString(); + + public String getName() { + return NAME; + } + + public Object fromStream(final byte[] iStream) throws IOException { + return new String(iStream,"UTF-8"); + } + + public byte[] toStream(final Object iObject) throws IOException { + return ((String) iObject).getBytes("UTF-8"); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringBuilderSerializable.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringBuilderSerializable.java new file mode 100644 index 00000000000..0651bb18268 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringBuilderSerializable.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.string; + +import com.orientechnologies.orient.core.exception.OSerializationException; + +public interface OStringBuilderSerializable { + public OStringBuilderSerializable toStream(StringBuilder iOutput) throws OSerializationException; + + public OStringBuilderSerializable fromStream(StringBuilder iInput) throws OSerializationException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializer.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializer.java new file mode 100644 index 00000000000..185566f5f65 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializer.java @@ -0,0 +1,28 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.string; + +public interface OStringSerializer { + public StringBuilder toStream(StringBuilder iOutput, Object iSource); + + public Object fromStream(String iSource); + + public String getName(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerAnyStreamable.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerAnyStreamable.java new file mode 100755 index 00000000000..7cd74028b59 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerAnyStreamable.java @@ -0,0 +1,86 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.string; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerHelper; + +public class OStringSerializerAnyStreamable implements OStringSerializer { + public static final OStringSerializerAnyStreamable INSTANCE = new OStringSerializerAnyStreamable(); + public static final String NAME = "st"; + + /** + * Re-Create any object if the class has a public constructor that accepts a String as unique parameter. + */ + public Object fromStream(final String iStream) { + if (iStream == null || iStream.length() == 0) + // NULL VALUE + return null; + + OSerializableStream instance = null; + + int propertyPos = iStream.indexOf(':'); + int pos = iStream.indexOf(OStreamSerializerHelper.SEPARATOR); + if (pos < 0 || propertyPos > -1 && pos > propertyPos) { + instance = new ODocument(); + pos = -1; + } else { + final String className = iStream.substring(0, pos); + try { + final Class clazz = Class.forName(className); + instance = (OSerializableStream) clazz.newInstance(); + } catch (Exception e) { + final String message = "Error on unmarshalling content. Class: " + className; + OLogManager.instance().error(this, message, e); + throw OException.wrapException(new OSerializationException(message), e); + } + } + + instance.fromStream(OBase64Utils.decode(iStream.substring(pos + 1))); + return instance; + } + + /** + * Serialize the class name size + class name + object content + * + * @param iValue + */ + public StringBuilder toStream(final StringBuilder iOutput, Object iValue) { + if (iValue != null) { + if (!(iValue instanceof OSerializableStream)) + throw new OSerializationException("Cannot serialize the object since it's not implements the OSerializableStream interface"); + + OSerializableStream stream = (OSerializableStream) iValue; + iOutput.append(iValue.getClass().getName()); + iOutput.append(OStreamSerializerHelper.SEPARATOR); + iOutput.append(OBase64Utils.encodeBytes(stream.toStream())); + } + return iOutput; + } + + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerEmbedded.java b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerEmbedded.java new file mode 100755 index 00000000000..4c8e06a3914 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/serialization/serializer/string/OStringSerializerEmbedded.java @@ -0,0 +1,116 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.serialization.serializer.string; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.exception.OSerializationException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.OBinaryProtocol; +import com.orientechnologies.orient.core.serialization.ODocumentSerializable; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV; +import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializerHelper; + +import java.io.UnsupportedEncodingException; + +public class OStringSerializerEmbedded implements OStringSerializer { + public static final OStringSerializerEmbedded INSTANCE = new OStringSerializerEmbedded(); + public static final String NAME = "em"; + + /** + * Re-Create any object if the class has a public constructor that accepts a String as unique parameter. + */ + public Object fromStream(final String iStream) { + if (iStream == null || iStream.length() == 0) + // NULL VALUE + return null; + + final ODocument instance = new ODocument(); + try { + ORecordSerializerSchemaAware2CSV.INSTANCE.fromStream(iStream.getBytes("UTF-8"), instance, null); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error decoding string"),e); + } + + final String className = instance.field(ODocumentSerializable.CLASS_NAME); + if (className == null) + return instance; + + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + OLogManager.instance().debug(this, "Class name provided in embedded document " + className + " does not exist."); + } + + if (clazz == null) + return instance; + + if (ODocumentSerializable.class.isAssignableFrom(clazz)) { + try { + final ODocumentSerializable documentSerializable = (ODocumentSerializable) clazz.newInstance(); + final ODocument docClone = new ODocument(); + instance.copyTo(docClone); + docClone.removeField(ODocumentSerializable.CLASS_NAME); + documentSerializable.fromDocument(docClone); + + return documentSerializable; + } catch (InstantiationException e) { + throw OException.wrapException(new OSerializationException("Cannot serialize the object"), e); + } catch (IllegalAccessException e) { + throw OException.wrapException(new OSerializationException("Cannot serialize the object"), e); + } + } + + return instance; + } + + /** + * Serialize the class name size + class name + object content + * + * @param iValue + */ + public StringBuilder toStream(final StringBuilder iOutput, Object iValue) { + if (iValue != null) { + if (iValue instanceof ODocumentSerializable) + iValue = ((ODocumentSerializable) iValue).toDocument(); + + if (!(iValue instanceof OSerializableStream)) + throw new OSerializationException("Cannot serialize the object since it's not implements the OSerializableStream interface"); + + OSerializableStream stream = (OSerializableStream) iValue; + iOutput.append(iValue.getClass().getName()); + iOutput.append(OStreamSerializerHelper.SEPARATOR); + try { + iOutput.append(new String(stream.toStream(),"UTF-8")); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OSerializationException("Error serializing embedded object"), e); + } + } + + return iOutput; + } + + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingClusterSelectionStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingClusterSelectionStrategy.java new file mode 100644 index 00000000000..8cad6d8ce75 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingClusterSelectionStrategy.java @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2014 Orient Technologies LTD (info(at)orientechnologies.com) + * + * Licensed 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 com.orientechnologies.orient.core.sharding.auto; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.exception.OInvalidIndexEngineIdException; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexEngine; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.List; + +/** + * Returns the cluster selecting through the hash function. + * + * @since 3.0 + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OAutoShardingClusterSelectionStrategy implements OClusterSelectionStrategy { + public static final String NAME = "auto-sharding"; + private final OIndex index; + private final OIndexEngine indexEngine; + private final List indexedFields; + private final int[] clusters; + + public OAutoShardingClusterSelectionStrategy(final OClass clazz, final OIndex autoShardingIndex) { + index = autoShardingIndex; + if (index == null) + throw new OConfigurationException( + "Cannot use auto-sharding cluster strategy because class '" + clazz + "' has no auto-sharding index defined"); + + indexedFields = index.getDefinition().getFields(); + if (indexedFields.size() != 1) + throw new OConfigurationException("Cannot use auto-sharding cluster strategy because class '" + clazz + + "' has an auto-sharding index defined with multiple fields"); + + final OStorage stg = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getUnderlying(); + if (!(stg instanceof OAbstractPaginatedStorage)) + throw new OConfigurationException("Cannot use auto-sharding cluster strategy because storage is not embedded"); + + try { + indexEngine = ((OAbstractPaginatedStorage) stg).getIndexEngine(index.getIndexId()); + } catch (OInvalidIndexEngineIdException e) { + throw new OConfigurationException("Cannot use auto-sharding cluster strategy because the underlying index has not found"); + } + + if (indexEngine == null) + throw new OConfigurationException("Cannot use auto-sharding cluster strategy because the underlying index has not found"); + + clusters = clazz.getClusterIds(); + } + + public int getCluster(final OClass clazz, final ODocument doc) { + final Object fieldValue = doc.field(indexedFields.get(0)); + + return clusters[((OAutoShardingIndexEngine) indexEngine).getStrategy().getPartitionsId(fieldValue, clusters.length)]; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexEngine.java b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexEngine.java new file mode 100755 index 00000000000..394b0d0d27e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexEngine.java @@ -0,0 +1,351 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sharding.auto; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.index.hashindex.local.OHashIndexBucket; +import com.orientechnologies.orient.core.index.hashindex.local.OHashTable; +import com.orientechnologies.orient.core.index.hashindex.local.OLocalHashTable; +import com.orientechnologies.orient.core.index.hashindex.local.OMurmurHash3HashFunction; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Index engine implementation that relies on multiple hash indexes partitioned by key. + * + * @author Luca Garulli + */ +public final class OAutoShardingIndexEngine implements OIndexEngine { + public static final int VERSION = 1; + public static final String SUBINDEX_METADATA_FILE_EXTENSION = ".asm"; + public static final String SUBINDEX_TREE_FILE_EXTENSION = ".ast"; + public static final String SUBINDEX_BUCKET_FILE_EXTENSION = ".asb"; + public static final String SUBINDEX_NULL_BUCKET_FILE_EXTENSION = ".asn"; + + private final OAbstractPaginatedStorage storage; + private final boolean durableInNonTx; + private final OMurmurHash3HashFunction hashFunction; + private List> partitions; + private OAutoShardingStrategy strategy; + private int version; + private final String name; + private int partitionSize; + + public OAutoShardingIndexEngine(final String iName, final Boolean iDurableInNonTxMode, final OAbstractPaginatedStorage iStorage, + final int iVersion) { + this.name = iName; + this.storage = iStorage; + this.hashFunction = new OMurmurHash3HashFunction(); + + if (iDurableInNonTxMode == null) + durableInNonTx = OGlobalConfiguration.INDEX_DURABLE_IN_NON_TX_MODE.getValueAsBoolean(); + else + durableInNonTx = iDurableInNonTxMode; + + this.version = iVersion; + } + + @Override + public String getName() { + return name; + } + + public OAutoShardingStrategy getStrategy() { + return strategy; + } + + @Override + public void create(final OBinarySerializer valueSerializer, final boolean isAutomatic, final OType[] keyTypes, + final boolean nullPointerSupport, final OBinarySerializer keySerializer, final int keySize, final Set clustersToIndex, + final Map engineProperties, final ODocument metadata) { + + this.strategy = new OAutoShardingMurmurStrategy(keySerializer); + this.hashFunction.setValueSerializer(keySerializer); + this.partitionSize = clustersToIndex.size(); + if (metadata != null && metadata.containsField("partitions")) + this.partitionSize = metadata.field("partitions"); + + engineProperties.put("partitions", "" + partitionSize); + + init(); + + for (OHashTable p : partitions) { + p.create(keySerializer, valueSerializer, keyTypes, nullPointerSupport); + } + } + + @Override + public void load(final String indexName, final OBinarySerializer valueSerializer, final boolean isAutomatic, + final OBinarySerializer keySerializer, final OType[] keyTypes, final boolean nullPointerSupport, final int keySize, + final Map engineProperties) { + + this.strategy = new OAutoShardingMurmurStrategy(keySerializer); + + final OStorage storage = getDatabase().getStorage().getUnderlying(); + if (storage instanceof OAbstractPaginatedStorage) { + final String partitionsAsString = engineProperties.get("partitions"); + if (partitionsAsString == null || partitionsAsString.isEmpty()) + throw new OIndexException( + "Cannot load autosharding index '" + indexName + "' because there is no metadata about the number of partitions"); + + partitionSize = Integer.parseInt(partitionsAsString); + init(); + + int i = 0; + for (OHashTable p : partitions) + p.load(indexName + "_" + (i++), keyTypes, nullPointerSupport); + } + + hashFunction.setValueSerializer(keySerializer); + } + + @Override + public void flush() { + if (partitions != null) + for (OHashTable p : partitions) + p.flush(); + } + + @Override + public void deleteWithoutLoad(final String indexName) { + if (partitions != null) + for (OHashTable p : partitions) + p.deleteWithoutLoad(indexName, (OAbstractPaginatedStorage) getDatabase().getStorage().getUnderlying()); + } + + @Override + public void delete() { + if (partitions != null) + for (OHashTable p : partitions) + p.delete(); + } + + @Override + public void init(final String indexName, final String indexType, final OIndexDefinition indexDefinition, + final boolean isAutomatic, final ODocument metadata) { + } + + private void init() { + if (partitions != null) + return; + + partitions = new ArrayList>(partitionSize); + for (int i = 0; i < partitionSize; ++i) { + partitions.add( + new OLocalHashTable(name + "_" + i, SUBINDEX_METADATA_FILE_EXTENSION, SUBINDEX_TREE_FILE_EXTENSION, + SUBINDEX_BUCKET_FILE_EXTENSION, SUBINDEX_NULL_BUCKET_FILE_EXTENSION, hashFunction, durableInNonTx, storage)); + } + } + + @Override + public boolean contains(final Object key) { + return getPartition(key).get(key) != null; + } + + @Override + public boolean remove(final Object key) { + return getPartition(key).remove(key) != null; + } + + @Override + public void clear() { + if (partitions != null) + for (OHashTable p : partitions) + p.clear(); + } + + @Override + public void close() { + if (partitions != null) + for (OHashTable p : partitions) + p.close(); + } + + @Override + public Object get(final Object key) { + return getPartition(key).get(key); + } + + @Override + public void put(final Object key, final Object value) { + getPartition(key).put(key, value); + } + + @SuppressWarnings("unchecked") + @Override + public boolean validatedPut(Object key, OIdentifiable value, Validator validator) { + return getPartition(key).validatedPut(key, value, (Validator) validator); + } + + @Override + public long size(final ValuesTransformer transformer) { + long counter = 0; + + if (partitions != null) + for (OHashTable p : partitions) { + if (transformer == null) + counter += p.size(); + else { + final OHashIndexBucket.Entry firstEntry = p.firstEntry(); + if (firstEntry == null) + continue; + + OHashIndexBucket.Entry[] entries = p.ceilingEntries(firstEntry.key); + + while (entries.length > 0) { + for (OHashIndexBucket.Entry entry : entries) + counter += transformer.transformFromValue(entry.value).size(); + + entries = p.higherEntries(entries[entries.length - 1].key); + } + } + } + return counter; + } + + @Override + public int getVersion() { + return version; + } + + @Override + public boolean hasRangeQuerySupport() { + return false; + } + + @Override + public OIndexCursor cursor(final ValuesTransformer valuesTransformer) { + throw new UnsupportedOperationException("cursor"); + } + + @Override + public OIndexCursor descCursor(final ValuesTransformer valuesTransformer) { + throw new UnsupportedOperationException("descCursor"); + } + + @Override + public OIndexKeyCursor keyCursor() { + return new OIndexKeyCursor() { + private int nextPartition = 1; + private OHashTable hashTable; + private int nextEntriesIndex; + private OHashIndexBucket.Entry[] entries; + + { + if (partitions == null || partitions.isEmpty()) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else { + hashTable = partitions.get(0); + OHashIndexBucket.Entry firstEntry = hashTable.firstEntry(); + if (firstEntry == null) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else + entries = hashTable.ceilingEntries(firstEntry.key); + } + } + + @Override + public Object next(final int prefetchSize) { + if (entries.length == 0) { + return null; + } + + final OHashIndexBucket.Entry bucketEntry = entries[nextEntriesIndex]; + nextEntriesIndex++; + if (nextEntriesIndex >= entries.length) { + entries = hashTable.higherEntries(entries[entries.length - 1].key); + nextEntriesIndex = 0; + + if (entries.length == 0 && nextPartition < partitions.size()) { + // GET NEXT PARTITION + hashTable = partitions.get(nextPartition++); + OHashIndexBucket.Entry firstEntry = hashTable.firstEntry(); + if (firstEntry == null) + entries = OCommonConst.EMPTY_BUCKET_ENTRY_ARRAY; + else + entries = hashTable.ceilingEntries(firstEntry.key); + } + } + + return bucketEntry.key; + } + }; + } + + @Override + public OIndexCursor iterateEntriesBetween(final Object rangeFrom, final boolean fromInclusive, final Object rangeTo, + final boolean toInclusive, boolean ascSortOrder, ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesBetween"); + } + + @Override + public OIndexCursor iterateEntriesMajor(final Object fromKey, final boolean isInclusive, final boolean ascSortOrder, + ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesMajor"); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean isInclusive, boolean ascSortOrder, ValuesTransformer transformer) { + throw new UnsupportedOperationException("iterateEntriesMinor"); + } + + @Override + public Object getFirstKey() { + throw new UnsupportedOperationException("firstKey"); + } + + @Override + public Object getLastKey() { + throw new UnsupportedOperationException("lastKey"); + } + + @Override + public boolean acquireAtomicExclusiveLock(final Object key) { + getPartition(key).acquireAtomicExclusiveLock(); + return false; + } + + @Override + public String getIndexNameByKey(final Object key) { + return getPartition(key).getName(); + } + + private ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + private OHashTable getPartition(final Object iKey) { + final int partitionId = iKey != null ? strategy.getPartitionsId(iKey, partitionSize) : 0; + return partitions.get(partitionId); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexFactory.java new file mode 100755 index 00000000000..b8503557091 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingIndexFactory.java @@ -0,0 +1,143 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sharding.auto; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OConfigurationException; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.index.engine.ORemoteIndexEngine; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +/** + * Auto-sharding index factory.
          + * Supports index types: + *
            + *
          • UNIQUE
          • + *
          • NOTUNIQUE
          • + *
          + * + * @since 3.0 + */ +public class OAutoShardingIndexFactory implements OIndexFactory { + + public static final String AUTOSHARDING_ALGORITHM = "AUTOSHARDING"; + public static final String NONE_VALUE_CONTAINER = "NONE"; + + private static final Set TYPES; + private static final Set ALGORITHMS; + + static { + final Set types = new HashSet(); + types.add(OClass.INDEX_TYPE.UNIQUE.toString()); + types.add(OClass.INDEX_TYPE.NOTUNIQUE.toString()); + TYPES = Collections.unmodifiableSet(types); + } + + static { + final Set algorithms = new HashSet(); + algorithms.add(AUTOSHARDING_ALGORITHM); + ALGORITHMS = Collections.unmodifiableSet(algorithms); + } + + public static boolean isMultiValueIndex(final String indexType) { + switch (OClass.INDEX_TYPE.valueOf(indexType)) { + case UNIQUE: + case UNIQUE_HASH_INDEX: + case DICTIONARY: + case DICTIONARY_HASH_INDEX: + return false; + } + + return true; + } + + /** + * Index types: + *
            + *
          • UNIQUE
          • + *
          • NOTUNIQUE
          • + *
          + */ + public Set getTypes() { + return TYPES; + } + + public Set getAlgorithms() { + return ALGORITHMS; + } + + public OIndexInternal createIndex(String name, ODatabaseDocumentInternal database, String indexType, String algorithm, + String valueContainerAlgorithm, ODocument metadata, int version) throws OConfigurationException { + if (valueContainerAlgorithm == null) + valueContainerAlgorithm = NONE_VALUE_CONTAINER; + + if (version < 0) + version = getLastVersion(); + + if (AUTOSHARDING_ALGORITHM.equals(algorithm)) + return createShardedIndex(name, indexType, valueContainerAlgorithm, metadata, + (OAbstractPaginatedStorage) database.getStorage().getUnderlying(), version); + + throw new OConfigurationException("Unsupported type: " + indexType); + } + + private OIndexInternal createShardedIndex(final String name, final String indexType, final String valueContainerAlgorithm, + final ODocument metadata, final OAbstractPaginatedStorage storage, final int version) { + + if (OClass.INDEX_TYPE.UNIQUE.toString().equals(indexType)) { + return new OIndexUnique(name, indexType, AUTOSHARDING_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } else if (OClass.INDEX_TYPE.NOTUNIQUE.toString().equals(indexType)) { + return new OIndexNotUnique(name, indexType, AUTOSHARDING_ALGORITHM, version, storage, valueContainerAlgorithm, metadata); + } + + throw new OConfigurationException("Unsupported type: " + indexType); + } + + @Override + public int getLastVersion() { + return OAutoShardingIndexEngine.VERSION; + } + + @Override + public OIndexEngine createIndexEngine(final String algorithm, final String name, final Boolean durableInNonTxMode, + final OStorage storage, final int version, final Map engineProperties) { + + final OIndexEngine indexEngine; + + final String storageType = storage.getType(); + if (storageType.equals("memory") || storageType.equals("plocal")) + indexEngine = new OAutoShardingIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage, version); + else if (storageType.equals("distributed")) + // DISTRIBUTED CASE: HANDLE IT AS FOR LOCAL + indexEngine = new OAutoShardingIndexEngine(name, durableInNonTxMode, (OAbstractPaginatedStorage) storage.getUnderlying(), + version); + else if (storageType.equals("remote")) + // MANAGE REMOTE SHARDED INDEX TO CALL THE INTERESTED SERVER + indexEngine = new ORemoteIndexEngine(name); + else + throw new OIndexException("Unsupported storage type: " + storageType); + + return indexEngine; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingMurmurStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingMurmurStrategy.java new file mode 100755 index 00000000000..1c6d9b2dd70 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingMurmurStrategy.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sharding.auto; + +import com.orientechnologies.common.serialization.types.OBinarySerializer; +import com.orientechnologies.orient.core.index.hashindex.local.OMurmurHash3HashFunction; + +import static java.lang.Math.abs; + +/** + * Auto-sharding strategy implementation that uses Murmur hashing. + * + * @since 3.0 + * @author Luca Garulli + */ +public final class OAutoShardingMurmurStrategy implements OAutoShardingStrategy { + private OMurmurHash3HashFunction hashFunction = new OMurmurHash3HashFunction(); + + public OAutoShardingMurmurStrategy(final OBinarySerializer keySerializer) { + hashFunction.setValueSerializer(keySerializer); + } + + public int getPartitionsId(final Object iKey, final int partitionSize) { + long hash = hashFunction.hashCode(iKey); + hash = hash == Long.MIN_VALUE ? 0 : abs(hash); + return (int) (hash % partitionSize); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingStrategy.java b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingStrategy.java new file mode 100755 index 00000000000..e4850d13071 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sharding/auto/OAutoShardingStrategy.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sharding.auto; + +/** + * Auto-sharding strategy interface. + * + * @since 3.0 + * @author Luca Garulli + */ +public interface OAutoShardingStrategy { + int getPartitionsId(Object iKey, int partitionSize); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/shutdown/OShutdownHandler.java b/core/src/main/java/com/orientechnologies/orient/core/shutdown/OShutdownHandler.java new file mode 100644 index 00000000000..85ca5299ffc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/shutdown/OShutdownHandler.java @@ -0,0 +1,51 @@ +package com.orientechnologies.orient.core.shutdown; + +import com.orientechnologies.orient.core.Orient; + +/** + * Handler which is used inside of shutdown priority queue. + * The higher priority we have the earlier this handler will be executed. + *

          + * There are set of predefined priorities which are used for system shutdown handlers which allows to add your handlers before , between and + * after them. + * + * @see Orient#addShutdownHandler(OShutdownHandler) + * @see Orient#shutdown() + */ +public interface OShutdownHandler { + /** + * Priority of {@link com.orientechnologies.orient.core.Orient.OShutdownWorkersHandler} handler. + */ + int SHUTDOWN_WORKERS_PRIORITY = 1000; + + /** + * Priority of {@link com.orientechnologies.orient.core.Orient.OShutdownEnginesHandler} handler. + */ + int SHUTDOWN_ENGINES_PRIORITY = 1100; + + /** + * Priority of {@link com.orientechnologies.orient.core.Orient.OShutdownPendingThreadsHandler} handler. + */ + int SHUTDOWN_PENDING_THREADS_PRIORITY = 1200; + + /** + * Priority of {@link com.orientechnologies.orient.core.Orient.OShutdownProfilerHandler} handler. + */ + int SHUTDOWN_PROFILER_PRIORITY = 1300; + + /** + * Priority of {@link com.orientechnologies.orient.core.Orient.OShutdownCallListenersHandler} handler. + */ + int SHUTDOWN_CALL_LISTENERS = 1400; + + /** + * @return Handlers priority. + */ + int getPriority(); + + /** + * Code which executed during system shutdown. During call of {@link Orient#shutdown()} method which is called during JVM + * shutdown. + */ + void shutdown() throws Exception; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OChainedIndexProxy.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OChainedIndexProxy.java new file mode 100644 index 00000000000..2fa51424eab --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OChainedIndexProxy.java @@ -0,0 +1,669 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.common.profiler.OProfilerStub; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.iterator.OEmptyIterator; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; + +import java.util.*; + +/** + *

          + * There are some cases when we need to create index for some class by traversed property. Unfortunately, such functionality is not + * supported yet. But we can do that by creating index for each element of {@link OSQLFilterItemField.FieldChain} (which define + * "way" to our property), and then process operations consequently using previously created indexes. + *

          + *

          + * This class provides possibility to find optimal chain of indexes and then use it just like it was index for traversed property. + *

          + *

          + * IMPORTANT: this class is only for internal usage! + *

          + * + * @author Artem Orobets + */ + +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class OChainedIndexProxy implements OIndex { + private final OIndex firstIndex; + + private final List> indexChain; + private final OIndex lastIndex; + + private OChainedIndexProxy(List> indexChain) { + this.firstIndex = (OIndex) indexChain.get(0); + this.indexChain = Collections.unmodifiableList(indexChain); + lastIndex = indexChain.get(indexChain.size() - 1); + } + + /** + * Create proxies that support maximum number of different operations. In case when several different indexes which support + * different operations (e.g. indexes of {@code UNIQUE} and {@code FULLTEXT} types) are possible, the creates the only one index + * of each type. + * + * @param longChain + * - property chain from the query, which should be evaluated + * @return proxies needed to process query. + */ + public static Collection> createProxies(OClass iSchemaClass, OSQLFilterItemField.FieldChain longChain) { + List> proxies = new ArrayList>(); + + for (List> indexChain : getIndexesForChain(iSchemaClass, longChain)) { + proxies.add(new OChainedIndexProxy(indexChain)); + } + + return proxies; + } + + private static boolean isComposite(OIndex currentIndex) { + return currentIndex.getDefinition().getParamCount() > 1; + } + + private static Iterable>> getIndexesForChain(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) { + List> baseIndexes = prepareBaseIndexes(iSchemaClass, fieldChain); + + if (baseIndexes == null) + return Collections.emptyList(); + + Collection> lastIndexes = prepareLastIndexVariants(iSchemaClass, fieldChain); + + Collection>> result = new ArrayList>>(); + for (OIndex lastIndex : lastIndexes) { + final List> indexes = new ArrayList>(fieldChain.getItemCount()); + indexes.addAll(baseIndexes); + indexes.add(lastIndex); + + result.add(indexes); + } + + return result; + } + + private static Collection> prepareLastIndexVariants(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) { + OClass oClass = iSchemaClass; + final Collection> result = new ArrayList>(); + + for (int i = 0; i < fieldChain.getItemCount() - 1; i++) { + oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass(); + if (oClass == null) { + return result; + } + } + + final Set> involvedIndexes = new TreeSet>(new Comparator>() { + public int compare(OIndex o1, OIndex o2) { + return o1.getDefinition().getParamCount() - o2.getDefinition().getParamCount(); + } + }); + + involvedIndexes.addAll(oClass.getInvolvedIndexes(fieldChain.getItemName(fieldChain.getItemCount() - 1))); + final Collection> indexTypes = new HashSet>(3); + + for (OIndex involvedIndex : involvedIndexes) { + if (!indexTypes.contains(involvedIndex.getInternal().getClass())) { + result.add(involvedIndex); + indexTypes.add(involvedIndex.getInternal().getClass()); + } + } + + return result; + } + + private static List> prepareBaseIndexes(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) { + List> result = new ArrayList>(fieldChain.getItemCount() - 1); + + OClass oClass = iSchemaClass; + for (int i = 0; i < fieldChain.getItemCount() - 1; i++) { + final Set> involvedIndexes = oClass.getInvolvedIndexes(fieldChain.getItemName(i)); + final OIndex bestIndex = findBestIndex(involvedIndexes); + + if (bestIndex == null) + return null; + + result.add(bestIndex); + oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass(); + } + return result; + } + + /** + * Finds the index that fits better as a base index in chain. + * + * Requirements to the base index: + *
            + *
          • Should be unique or not unique. Other types cannot be used to get all documents with required links.
          • + *
          • Should not be composite hash index. As soon as hash index does not support partial match search.
          • + *
          • Composite index that ignores null values should not be used.
          • + *
          • Hash index is better than tree based indexes.
          • + *
          • Non composite indexes is better that composite.
          • + *
          + * + * @param indexes + * where search + * @return the index that fits better as a base index in chain + */ + protected static OIndex findBestIndex(Iterable> indexes) { + OIndex bestIndex = null; + for (OIndex index : indexes) { + if (priorityOfUsage(index) > priorityOfUsage(bestIndex)) + bestIndex = index; + } + return bestIndex; + } + + private static int priorityOfUsage(OIndex index) { + if (index == null) + return -1; + + final OClass.INDEX_TYPE indexType = OClass.INDEX_TYPE.valueOf(index.getType()); + final boolean isComposite = isComposite(index); + final boolean supportNullValues = supportNullValues(index); + + int priority = 1; + + if (isComposite) { + if (!supportNullValues) + return -1; + } else { + priority += 10; + } + + switch (indexType) { + case UNIQUE_HASH_INDEX: + case NOTUNIQUE_HASH_INDEX: + if (isComposite) + return -1; + else + priority += 10; + break; + case UNIQUE: + case NOTUNIQUE: + priority += 5; + break; + case PROXY: + case FULLTEXT: + case DICTIONARY: + case FULLTEXT_HASH_INDEX: + case DICTIONARY_HASH_INDEX: + case SPATIAL: + return -1; + } + + return priority; + } + + /** + * Checks if index can be used as base index. + * + * Requirements to the base index: + *
            + *
          • Should be unique or not unique. Other types cannot be used to get all documents with required links.
          • + *
          • Should not be composite hash index. As soon as hash index does not support partial match search.
          • + *
          • Composite index that ignores null values should not be used.
          • + *
          + * + * @param index + * to check + * @return true if index usage is allowed as base index. + */ + public static boolean isAppropriateAsBase(OIndex index) { + return priorityOfUsage(index) > 0; + } + + /** + * {@inheritDoc} + */ + @Override + public long getRebuildVersion() { + long rebuildVersion = 0; + + for (OIndex index : indexChain) { + rebuildVersion += index.getRebuildVersion(); + } + + return rebuildVersion; + } + + private static boolean supportNullValues(OIndex index) { + final ODocument metadata = index.getMetadata(); + if (metadata == null) + return false; + + final Boolean ignoreNullValues = metadata.field("ignoreNullValues"); + return Boolean.FALSE.equals(ignoreNullValues); + } + + public String getDatabaseName() { + return firstIndex.getDatabaseName(); + } + + public List getIndexNames() { + final ArrayList names = new ArrayList(indexChain.size()); + for (OIndex oIndex : indexChain) { + names.add(oIndex.getName()); + } + + return names; + } + + @Override + public String getName() { + final StringBuilder res = new StringBuilder("IndexChain{"); + final List indexNames = getIndexNames(); + + for (int i = 0; i < indexNames.size(); i++) { + String indexName = indexNames.get(i); + if (i > 0) + res.append(", "); + res.append(indexName); + } + + res.append("}"); + + return res.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public T get(Object iKey) { + final Object lastIndexResult = lastIndex.get(iKey); + + final Set result = new HashSet(); + + if (lastIndexResult != null) + result.addAll(applyTailIndexes(lastIndexResult)); + + return (T) result; + } + + /** + * Returns internal index of last chain index, because proxy applicable to all operations that last index applicable. + */ + public OIndexInternal getInternal() { + return (OIndexInternal) lastIndex.getInternal(); + } + + /** + * {@inheritDoc} + */ + public OIndexDefinition getDefinition() { + return lastIndex.getDefinition(); + } + + private List applyTailIndexes(final Object lastIndexResult) { + final OIndex beforeTheLastIndex = indexChain.get(indexChain.size() - 2); + Set currentKeys = prepareKeys(beforeTheLastIndex, lastIndexResult); + + for (int j = indexChain.size() - 2; j > 0; j--) { + final OIndex currentIndex = indexChain.get(j); + final OIndex nextIndex = indexChain.get(j - 1); + + final Set newKeys; + if (isComposite(currentIndex)) { + newKeys = new TreeSet(); + for (Comparable currentKey : currentKeys) { + final List currentResult = getFromCompositeIndex(currentKey, currentIndex); + newKeys.addAll(prepareKeys(nextIndex, currentResult)); + } + } else { + final OIndexCursor cursor = currentIndex.iterateEntries(currentKeys, true); + final List keys = cursorToList(cursor); + newKeys = prepareKeys(nextIndex, keys); + } + + updateStatistic(currentIndex); + + currentKeys = newKeys; + } + + return applyFirstIndex(currentKeys); + } + + private List applyFirstIndex(Collection currentKeys) { + final List result; + if (isComposite(firstIndex)) { + result = new ArrayList(); + for (Comparable key : currentKeys) { + result.addAll(getFromCompositeIndex(key, firstIndex)); + } + } else { + final OIndexCursor cursor = firstIndex.iterateEntries(currentKeys, true); + + result = cursorToList(cursor); + } + + updateStatistic(firstIndex); + + return result; + } + + private List getFromCompositeIndex(Comparable currentKey, OIndex currentIndex) { + final OIndexCursor cursor = currentIndex.iterateEntriesBetween(currentKey, true, currentKey, true, true); + + return cursorToList(cursor); + } + + private List cursorToList(OIndexCursor cursor) { + final List currentResult = new ArrayList(); + Map.Entry entry = cursor.nextEntry(); + while (entry != null) { + currentResult.add(entry.getValue()); + entry = cursor.nextEntry(); + } + return currentResult; + } + + /** + * Make type conversion of keys for specific index. + * + * @param index + * - index for which keys prepared for. + * @param keys + * - which should be prepared. + * @return keys converted to necessary type. + */ + private Set prepareKeys(OIndex index, Object keys) { + final OIndexDefinition indexDefinition = index.getDefinition(); + if (keys instanceof Collection) { + final Set newKeys = new TreeSet(); + for (Object o : ((Collection) keys)) { + newKeys.add((Comparable) indexDefinition.createValue(o)); + } + return newKeys; + } else { + return Collections.singleton((Comparable) indexDefinition.createValue(keys)); + } + } + + /** + * Register statistic information about usage of index in {@link OProfilerStub}. + * + * @param index + * which usage is registering. + */ + private void updateStatistic(OIndex index) { + + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + Orient.instance().getProfiler().updateCounter(profiler.getDatabaseMetric(index.getDatabaseName(), "query.indexUsed"), + "Used index in query", +1); + + final int paramCount = index.getDefinition().getParamCount(); + if (paramCount > 1) { + final String profiler_prefix = profiler.getDatabaseMetric(index.getDatabaseName(), "query.compositeIndexUsed"); + profiler.updateCounter(profiler_prefix, "Used composite index in query", +1); + profiler.updateCounter(profiler_prefix + "." + paramCount, "Used composite index in query with " + paramCount + " params", + +1); + } + } + } + + public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) { + return firstIndex.checkEntry(iRecord, iKey); + } + + // + // Following methods are not allowed for proxy. + // + + @Override + public OIndex create(String name, OIndexDefinition indexDefinition, String clusterIndexName, Set clustersToIndex, + boolean rebuild, OProgressListener progressListener) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public boolean contains(Object iKey) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public OType[] getKeyTypes() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public Iterator> iterator() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public OIndex put(Object iKey, OIdentifiable iValue) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public boolean remove(Object key) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public boolean remove(Object iKey, OIdentifiable iRID) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public OIndex clear() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public Iterable keys() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public long getSize() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public long count(Object iKey) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public long getKeySize() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public void flush() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public OIndex delete() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public String getType() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public String getAlgorithm() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public boolean isAutomatic() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public long rebuild() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public long rebuild(OProgressListener iProgressListener) { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public ODocument getConfiguration() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public ODocument getMetadata() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public ORID getIdentity() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public Set getClusters() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public Object getFirstKey() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public Object getLastKey() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public int getIndexId() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public boolean isUnique() { + return firstIndex.isUnique(); + } + + @Override + public OIndexCursor cursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexCursor descCursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + @Override + public OIndexKeyCursor keyCursor() { + throw new UnsupportedOperationException("Not allowed operation"); + } + + public boolean supportsOrderedIterations() { + return false; + } + + @Override + public OIndexCursor iterateEntries(Collection keys, boolean ascSortOrder) { + final OIndexCursor internalCursor = lastIndex.iterateEntries(keys, ascSortOrder); + return new ExternalIndexCursor(internalCursor); + } + + @Override + public OIndexCursor iterateEntriesBetween(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, + boolean ascOrder) { + final OIndexCursor internalCursor = lastIndex.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder); + return new ExternalIndexCursor(internalCursor); + } + + @Override + public OIndexCursor iterateEntriesMajor(Object fromKey, boolean fromInclusive, boolean ascOrder) { + final OIndexCursor internalCursor = lastIndex.iterateEntriesMajor(fromKey, fromInclusive, ascOrder); + return new ExternalIndexCursor(internalCursor); + } + + @Override + public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) { + final OIndexCursor internalCursor = lastIndex.iterateEntriesMinor(toKey, toInclusive, ascOrder); + return new ExternalIndexCursor(internalCursor); + } + + @Override + public boolean isRebuilding() { + return false; + } + + @Override + public int getVersion() { + return -1; + } + + private final class ExternalIndexCursor extends OIndexAbstractCursor { + private final OIndexCursor internalCursor; + + private final List queryResult = new ArrayList(); + private Iterator currentIterator = OEmptyIterator.IDENTIFIABLE_INSTANCE; + + private ExternalIndexCursor(OIndexCursor internalCursor) { + this.internalCursor = internalCursor; + } + + @Override + public Map.Entry nextEntry() { + if (currentIterator == null) + return null; + + while (!currentIterator.hasNext()) { + final Map.Entry entry = internalCursor.nextEntry(); + + if (entry == null) { + currentIterator = null; + return null; + } + + queryResult.clear(); + queryResult.addAll(applyTailIndexes(entry.getValue())); + + currentIterator = queryResult.iterator(); + } + + if (!currentIterator.hasNext()) { + currentIterator = null; + return null; + } + + final OIdentifiable result = currentIterator.next(); + + return new Map.Entry() { + @Override + public Object getKey() { + throw new UnsupportedOperationException("getKey"); + } + + @Override + public OIdentifiable getValue() { + return result; + } + + @Override + public OIdentifiable setValue(OIdentifiable value) { + throw new UnsupportedOperationException("setValue"); + } + }; + } + } + + @Override + public int compareTo(OIndex o) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAbstract.java new file mode 100755 index 00000000000..9dabf88a5c5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAbstract.java @@ -0,0 +1,255 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext.TIMEOUT_STRATEGY; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandExecutorAbstract; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestAbstract; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.sql.parser.OStatement; +import com.orientechnologies.orient.core.sql.parser.OStatementCache; + +import java.util.*; + +/** + * SQL abstract Command Executor implementation. + * + * @author Luca Garulli + */ +public abstract class OCommandExecutorSQLAbstract extends OCommandExecutorAbstract { + + public static final String KEYWORD_FROM = "FROM"; + public static final String KEYWORD_LET = "LET"; + public static final String KEYWORD_WHERE = "WHERE"; + public static final String KEYWORD_LIMIT = "LIMIT"; + public static final String KEYWORD_SKIP = "SKIP"; + public static final String KEYWORD_OFFSET = "OFFSET"; + public static final String KEYWORD_TIMEOUT = "TIMEOUT"; + public static final String KEYWORD_LOCK = "LOCK"; + public static final String KEYWORD_RETURN = "RETURN"; + public static final String KEYWORD_KEY = "key"; + public static final String KEYWORD_RID = "rid"; + public static final String CLUSTER_PREFIX = "CLUSTER:"; + public static final String CLASS_PREFIX = "CLASS:"; + public static final String INDEX_PREFIX = "INDEX:"; + public static final String KEYWORD_UNSAFE = "UNSAFE"; + + public static final String INDEX_VALUES_PREFIX = "INDEXVALUES:"; + public static final String INDEX_VALUES_ASC_PREFIX = "INDEXVALUESASC:"; + public static final String INDEX_VALUES_DESC_PREFIX = "INDEXVALUESDESC:"; + + public static final String DICTIONARY_PREFIX = "DICTIONARY:"; + public static final String METADATA_PREFIX = "METADATA:"; + public static final String METADATA_SCHEMA = "SCHEMA"; + public static final String METADATA_INDEXMGR = "INDEXMANAGER"; + + public static final String DEFAULT_PARAM_USER = "$user"; + + protected long timeoutMs = OGlobalConfiguration.COMMAND_TIMEOUT.getValueAsLong(); + protected TIMEOUT_STRATEGY timeoutStrategy = TIMEOUT_STRATEGY.EXCEPTION; + protected OStatement preParsedStatement; + + public OCommandExecutorSQLAbstract() { + timeoutStrategy = OGlobalConfiguration.QUERY_TIMEOUT_DEFAULT_STRATEGY.getValueAsString().equalsIgnoreCase("RETURN") ? + TIMEOUT_STRATEGY.RETURN: + TIMEOUT_STRATEGY.EXCEPTION; + } + + /** + * The command is replicated + * + * @return + */ + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE.REPLICATE; + } + + public boolean isIdempotent() { + return false; + } + + protected void throwSyntaxErrorException(final String iText) { + throw new OCommandSQLParsingException(iText + ". Use " + getSyntax(), parserText, parserGetPreviousPosition()); + } + + protected void throwParsingException(final String iText) { + throw new OCommandSQLParsingException(iText, parserText, parserGetPreviousPosition()); + } + + protected void throwParsingException(final String iText, Exception e) { + throw OException.wrapException(new OCommandSQLParsingException(iText, parserText, parserGetPreviousPosition()), e); + } + + /** + * Parses the timeout keyword if found. + */ + protected boolean parseTimeout(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_TIMEOUT)) + return false; + + String word = parserNextWord(true); + + try { + timeoutMs = Long.parseLong(word); + } catch (Exception e) { + throwParsingException( + "Invalid " + KEYWORD_TIMEOUT + " value set to '" + word + "' but it should be a valid long. Example: " + KEYWORD_TIMEOUT + + " 3000"); + } + + if (timeoutMs < 0) + throwParsingException("Invalid " + KEYWORD_TIMEOUT + ": value set minor than ZERO. Example: " + KEYWORD_TIMEOUT + " 10000"); + + word = parserNextWord(true); + + if (word != null) + if (word.equals(TIMEOUT_STRATEGY.EXCEPTION.toString())) + timeoutStrategy = TIMEOUT_STRATEGY.EXCEPTION; + else if (word.equals(TIMEOUT_STRATEGY.RETURN.toString())) + timeoutStrategy = TIMEOUT_STRATEGY.RETURN; + else + parserGoBack(); + + return true; + } + + /** + * Parses the lock keyword if found. + */ + protected String parseLock() throws OCommandSQLParsingException { + final String lockStrategy = parserNextWord(true); + + if (!lockStrategy.equalsIgnoreCase("DEFAULT") && !lockStrategy.equalsIgnoreCase("NONE") && !lockStrategy + .equalsIgnoreCase("RECORD")) + throwParsingException( + "Invalid " + KEYWORD_LOCK + " value set to '" + lockStrategy + "' but it should be NONE (default) or RECORD. Example: " + + KEYWORD_LOCK + " RECORD"); + + return lockStrategy; + } + + protected Set getInvolvedClustersOfClasses(final Collection iClassNames) { + final ODatabaseDocument db = getDatabase(); + + final Set clusters = new HashSet(); + + for (String clazz : iClassNames) { + final OClass cls = ((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot().getClass(clazz); + if (cls != null) + for (int clId : cls.getPolymorphicClusterIds()) { + // FILTER THE CLUSTER WHERE THE USER HAS THE RIGHT ACCESS + if (clId > -1 && checkClusterAccess(db, db.getClusterNameById(clId))) + clusters.add(db.getClusterNameById(clId).toLowerCase(Locale.ENGLISH)); + } + } + + return clusters; + } + + protected Set getInvolvedClustersOfClusters(final Collection iClusterNames) { + final ODatabaseDocument db = getDatabase(); + + final Set clusters = new HashSet(); + + for (String cluster : iClusterNames) { + final String c = cluster.toLowerCase(Locale.ENGLISH); + // FILTER THE CLUSTER WHERE THE USER HAS THE RIGHT ACCESS + if (checkClusterAccess(db, c)) + clusters.add(c); + } + + return clusters; + } + + protected Set getInvolvedClustersOfIndex(final String iIndexName) { + final ODatabaseDocumentInternal db = getDatabase(); + + final Set clusters = new HashSet(); + + final OMetadataInternal metadata = (OMetadataInternal) db.getMetadata(); + final OIndex idx = metadata.getIndexManager().getIndex(iIndexName); + if (idx != null && idx.getDefinition() != null) { + final String clazz = idx.getDefinition().getClassName(); + + if (clazz != null) { + final OClass cls = metadata.getImmutableSchemaSnapshot().getClass(clazz); + if (cls != null) + for (int clId : cls.getClusterIds()) { + final String clName = db.getClusterNameById(clId); + if (clName != null) + clusters.add(clName.toLowerCase(Locale.ENGLISH)); + } + } + } + + return clusters; + } + + protected boolean checkClusterAccess(final ODatabaseDocument db, final String iClusterName) { + return db.getUser() == null + || db.getUser().checkIfAllowed(ORule.ResourceGeneric.CLUSTER, iClusterName, getSecurityOperationType()) != null; + } + + protected void bindDefaultContextVariables() { + if (context != null) { + if (getDatabase() != null && getDatabase().getUser() != null) { + context.setVariable(DEFAULT_PARAM_USER, getDatabase().getUser().getIdentity()); + } + } + } + + protected String preParse(final String queryText, final OCommandRequest iRequest) { + final boolean strict = getDatabase().getStorage().getConfiguration().isStrictSql(); + if (strict) { + try { + final OStatement result = OStatementCache.get(queryText, getDatabase()); + preParsedStatement = result; + + if (iRequest instanceof OCommandRequestAbstract) { + final Map params = ((OCommandRequestAbstract) iRequest).getParameters(); + StringBuilder builder = new StringBuilder(); + result.toString(params, builder); + return builder.toString(); + } + return result.toString(); + } catch (OCommandSQLParsingException sqlx) { + throw sqlx; + } catch (Exception e) { + throwParsingException("Error parsing query: \n" + queryText + "\n" + e.getMessage(), e); + } + } + return queryText; + } + + protected String decodeClassName(String s) { + return OClassImpl.decodeClassName(s); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterClass.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterClass.java new file mode 100755 index 00000000000..d407c9812db --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterClass.java @@ -0,0 +1,204 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.sql.parser.OAlterClassStatement; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * SQL ALTER PROPERTY command: Changes an attribute of an existent property in the target class. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLAlterClass extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_ALTER = "ALTER"; + public static final String KEYWORD_CLASS = "CLASS"; + + private String className; + private ATTRIBUTES attribute; + private String value; + private boolean unsafe = false; + + public OCommandExecutorSQLAlterClass parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ALTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ALTER + " not found", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLASS)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLASS + " not found", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected ", parserText, oldPos); + + className = decodeClassName(word.toString()); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missed the class's attribute to change", parserText, oldPos); + + final String attributeAsString = word.toString(); + + try { + attribute = OClass.ATTRIBUTES.valueOf(attributeAsString.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + throw new OCommandSQLParsingException("Unknown class's attribute '" + attributeAsString + "'. Supported attributes are: " + + Arrays.toString(OClass.ATTRIBUTES.values()), parserText, oldPos); + } + + value = parserText.substring(pos + 1).trim(); + + if("addcluster".equalsIgnoreCase(attributeAsString) || "removecluster".equalsIgnoreCase(attributeAsString) ){ + value = decodeClassName(value); + } + if("description".equalsIgnoreCase(attributeAsString) ){ + if(value.length() >1 && '"' == value.charAt(0) && '"' == value.charAt(value.length() -1) ) { + value = value.substring(1); + value = value.substring(0, value.length() - 1); + } + } + + OAlterClassStatement stm = (OAlterClassStatement) preParsedStatement; + if (this.preParsedStatement != null && stm.property == ATTRIBUTES.CUSTOM) { + value = "" + stm.customKey.getStringValue() + "=" + stm.customValue.toString(); + } + + if (parserTextUpperCase.endsWith("UNSAFE")) { + unsafe = true; + value = value.substring(0, value.length() - "UNSAFE".length()); + for (int i = value.length() - 1; value.charAt(i) == ' ' || value.charAt(i) == '\t'; i--) + value = value.substring(0, value.length() - 1); + } + if (value.length() == 0) + throw new OCommandSQLParsingException("Missed the property's value to change for attribute '" + attribute + "'", parserText, + oldPos); + + if (value.equalsIgnoreCase("null")) + value = null; + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the ALTER CLASS. + */ + public Object execute(final Map iArgs) { + final ODatabaseDocument database = getDatabase(); + + if (attribute == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final OClassImpl cls = (OClassImpl) database.getMetadata().getSchema().getClass(className); + if (cls == null) + throw new OCommandExecutionException("Cannot alter class '" + className + "' because not found"); + + if (!unsafe && attribute == ATTRIBUTES.NAME && cls.isSubClassOf("E")) + throw new OCommandExecutionException("Cannot alter class '" + className + + "' because is an Edge class and could break vertices. Use UNSAFE if you want to force it"); + + // REMOVE CACHE OF COMMAND RESULTS + for (int clId : cls.getPolymorphicClusterIds()) + getDatabase().getMetadata().getCommandCache().invalidateResultsOfCluster(getDatabase().getClusterNameById(clId)); + + if (value != null && attribute == ATTRIBUTES.SUPERCLASS) { + checkClassExists(database, className, decodeClassName(value)); + } + if (value != null && attribute == ATTRIBUTES.SUPERCLASSES) { + List classes = Arrays.asList(value.split(",\\s*")); + for (String cName : classes) { + checkClassExists(database, className, decodeClassName(cName)); + } + } + if (!unsafe && value != null && attribute == ATTRIBUTES.NAME) { + if (!cls.getIndexes().isEmpty()) { + throw new OCommandExecutionException("Cannot rename class '" + className + + "' because it has indexes defined on it. Drop indexes before or use UNSAFE (at your won risk)"); + } + } + cls.set(attribute, value); + + return Boolean.TRUE; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + protected void checkClassExists(ODatabaseDocument database, String targetClass, String superClass) { + if (superClass.startsWith("+") || superClass.startsWith("-")) { + superClass = superClass.substring(1); + } + if (database.getMetadata().getSchema().getClass(decodeClassName(superClass)) == null) { + throw new OCommandExecutionException("Cannot alter superClass of '" + targetClass + "' because " + superClass + + " class not found"); + } + } + + public String getSyntax() { + return "ALTER CLASS [UNSAFE]"; + } + + @Override + public boolean involveSchema() { + return true; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterCluster.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterCluster.java new file mode 100755 index 00000000000..38d5b32c57e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterCluster.java @@ -0,0 +1,187 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.OCluster.ATTRIBUTES; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * SQL ALTER CLUSTER command: Changes an attribute of an existing cluster + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLAlterCluster extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_ALTER = "ALTER"; + public static final String KEYWORD_CLUSTER = "CLUSTER"; + + protected String clusterName; + protected int clusterId = -1; + protected ATTRIBUTES attribute; + protected String value; + + public OCommandExecutorSQLAlterCluster parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ALTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ALTER + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLUSTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLUSTER + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected . Use " + getSyntax(), parserText, oldPos); + + clusterName = word.toString(); + clusterName = decodeClassName(clusterName); + + final Pattern p = Pattern.compile("([0-9]*)"); + final Matcher m = p.matcher(clusterName); + if (m.matches()) + clusterId = Integer.parseInt(clusterName); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missing cluster attribute to change. Use " + getSyntax(), parserText, oldPos); + + final String attributeAsString = word.toString(); + + try { + attribute = OCluster.ATTRIBUTES.valueOf(attributeAsString.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + throw new OCommandSQLParsingException( + "Unknown class attribute '" + attributeAsString + "'. Supported attributes are: " + Arrays + .toString(OCluster.ATTRIBUTES.values()), parserText, oldPos); + } + + value = parserText.substring(pos + 1).trim(); + + value = decodeClassName(value); + + if (attribute == ATTRIBUTES.NAME) { + value = value.replaceAll(" ", ""); //no spaces in cluster names + } + + if (value.length() == 0) + throw new OCommandSQLParsingException( + "Missing property value to change for attribute '" + attribute + "'. Use " + getSyntax(), parserText, oldPos); + + if (value.equalsIgnoreCase("null")) + value = null; + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the ALTER CLUSTER. + */ + public Object execute(final Map iArgs) { + if (attribute == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final List clusters = getClusters(); + + if (clusters.isEmpty()) + throw new OCommandExecutionException("Cluster '" + clusterName + "' not found"); + + Object result = null; + + for (OCluster cluster : getClusters()) { + if (clusterId > -1 && clusterName.equals(String.valueOf(clusterId))) { + clusterName = cluster.getName(); + result = getDatabase().alterCluster(clusterName, attribute, value); + } else { + clusterId = cluster.getId(); + result = getDatabase().alterCluster(clusterId, attribute, value); + } + } + + return result; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + protected List getClusters() { + final ODatabaseDocumentInternal database = getDatabase(); + + final List result = new ArrayList(); + + if (clusterName.endsWith("*")) { + final String toMatch = clusterName.substring(0, clusterName.length() - 1).toLowerCase(Locale.ENGLISH); + for (String cl : database.getStorage().getClusterNames()) { + if (cl.startsWith(toMatch)) + result.add(database.getStorage().getClusterByName(cl)); + } + } else { + if (clusterId > -1) { + result.add(database.getStorage().getClusterById(clusterId)); + } else { + result.add(database.getStorage().getClusterById(database.getStorage().getClusterIdByName(clusterName))); + } + } + + return result; + } + + public String getSyntax() { + return "ALTER CLUSTER | "; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterDatabase.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterDatabase.java new file mode 100755 index 00000000000..10971ce6afa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterDatabase.java @@ -0,0 +1,128 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; + +/** + * SQL ALTER DATABASE command: Changes an attribute of the current database. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLAlterDatabase extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_ALTER = "ALTER"; + public static final String KEYWORD_DATABASE = "DATABASE"; + + private ODatabase.ATTRIBUTES attribute; + private String value; + + public OCommandExecutorSQLAlterDatabase parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ALTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ALTER + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DATABASE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DATABASE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missed the database's attribute to change. Use " + getSyntax(), parserText, oldPos); + + final String attributeAsString = word.toString(); + + try { + attribute = ODatabase.ATTRIBUTES.valueOf(attributeAsString.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + throw new OCommandSQLParsingException("Unknown database's attribute '" + attributeAsString + "'. Supported attributes are: " + + Arrays.toString(ODatabase.ATTRIBUTES.values()), parserText, oldPos); + } + + value = parserText.substring(pos + 1).trim(); + + if (value.length() == 0) + throw new OCommandSQLParsingException("Missed the database's value to change for attribute '" + attribute + "'. Use " + + getSyntax(), parserText, oldPos); + + if (value.equalsIgnoreCase("null")) + value = null; + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Execute the ALTER DATABASE. + */ + public Object execute(final Map iArgs) { + if (attribute == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocumentInternal database = getDatabase(); + database.checkSecurity(ORule.ResourceGeneric.DATABASE, ORole.PERMISSION_UPDATE); + + database.setInternal(attribute, value); + return null; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + public String getSyntax() { + return "ALTER DATABASE "; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterProperty.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterProperty.java new file mode 100755 index 00000000000..eeab57fde9b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterProperty.java @@ -0,0 +1,217 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OProperty.ATTRIBUTES; +import com.orientechnologies.orient.core.metadata.schema.OPropertyImpl; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.sql.parser.OAlterPropertyStatement; +import com.orientechnologies.orient.core.sql.parser.OExpression; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.util.Arrays; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + +/** + * SQL ALTER PROPERTY command: Changes an attribute of an existent property in the target class. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") public class OCommandExecutorSQLAlterProperty extends OCommandExecutorSQLAbstract + implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_ALTER = "ALTER"; + public static final String KEYWORD_PROPERTY = "PROPERTY"; + + private String className; + private String fieldName; + private ATTRIBUTES attribute; + private String value; + + public OCommandExecutorSQLAlterProperty parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ALTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ALTER + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_PROPERTY)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_PROPERTY + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, oldPos); + + String[] parts = word.toString().split("\\."); + if (parts.length != 2) { + if (parts[1].startsWith("`") && parts[parts.length - 1].endsWith("`")) { + StringBuilder fullName = new StringBuilder(); + for (int i = 1; i < parts.length; i++) { + if (i > 1) { + fullName.append("."); + } + fullName.append(parts[i]); + } + parts = new String[] { parts[0], fullName.toString() }; + } else { + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, oldPos); + } + } + + className = decodeClassName(parts[0]); + if (className == null) + throw new OCommandSQLParsingException("Class not found", parserText, oldPos); + fieldName = decodeClassName(parts[1]); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missing property attribute to change. Use " + getSyntax(), parserText, oldPos); + + final String attributeAsString = word.toString(); + + try { + attribute = OProperty.ATTRIBUTES.valueOf(attributeAsString.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + throw new OCommandSQLParsingException( + "Unknown property attribute '" + attributeAsString + "'. Supported attributes are: " + Arrays + .toString(OProperty.ATTRIBUTES.values()), parserText, oldPos); + } + + value = parserText.substring(pos + 1).trim(); + if (attribute.equals(ATTRIBUTES.NAME) || attribute.equals(ATTRIBUTES.LINKEDCLASS)) { + value = decodeClassName(value); + } + + if (value.length() == 0) { + throw new OCommandSQLParsingException( + "Missing property value to change for attribute '" + attribute + "'. Use " + getSyntax(), parserText, oldPos); + } + + if (preParsedStatement != null) { + OAlterPropertyStatement stm = (OAlterPropertyStatement) preParsedStatement; + OExpression settingExp = stm.settingValue; + if (settingExp != null) { + Object expValue = settingExp.execute(null, context); + if (expValue == null) { + expValue = settingExp.toString(); + } + if (expValue != null) { + if (expValue instanceof Date) { + value = ODateHelper.getDateTimeFormatInstance().format((Date) expValue); + } else + value = expValue.toString(); + } else + value = null; + if (attribute.equals(ATTRIBUTES.NAME) || attribute.equals(ATTRIBUTES.LINKEDCLASS)) { + value = decodeClassName(value); + } + } else if (stm.customPropertyName != null) { + value = "" + stm.customPropertyName.getStringValue() + "=" + stm.customPropertyValue.toString(); + } else if (stm.clearCustom) { + value = "clear"; + } + } else { + if (value.equalsIgnoreCase("null")) { + value = null; + } + if (value != null && isQuoted(value)) { + value = removeQuotes(value); + } + } + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + private String removeQuotes(String s) { + s = s.trim(); + return s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\""); + } + + private boolean isQuoted(String s) { + s = s.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) + return true; + if (s.startsWith("'") && s.endsWith("'")) + return true; + if (s.startsWith("`") && s.endsWith("`")) + return true; + + return false; + } + + @Override public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + /** + * Execute the ALTER PROPERTY. + */ + public Object execute(final Map iArgs) { + if (attribute == null) + throw new OCommandExecutionException("Cannot execute the command because it has not yet been parsed"); + + final OClassImpl sourceClass = (OClassImpl) getDatabase().getMetadata().getSchema().getClass(className); + if (sourceClass == null) + throw new OCommandExecutionException("Source class '" + className + "' not found"); + + final OPropertyImpl prop = (OPropertyImpl) sourceClass.getProperty(fieldName); + if (prop == null) + throw new OCommandExecutionException("Property '" + className + "." + fieldName + "' not exists"); + + if ("null".equalsIgnoreCase(value)) + prop.set(attribute, null); + else + prop.set(attribute, value); + return null; + } + + public String getSyntax() { + return "ALTER PROPERTY . "; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterSequence.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterSequence.java new file mode 100644 index 00000000000..571c2cf90a9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLAlterSequence.java @@ -0,0 +1,93 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; + +import java.util.Map; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 3/5/2015 + */ +public class OCommandExecutorSQLAlterSequence extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_ALTER = "ALTER"; + public static final String KEYWORD_SEQUENCE = "SEQUENCE"; + public static final String KEYWORD_START = "START"; + public static final String KEYWORD_INCREMENT = "INCREMENT"; + public static final String KEYWORD_CACHE = "CACHE"; + + private String sequenceName; + private OSequence.CreateParams params; + + @Override + public OCommandExecutorSQLAlterSequence parse(OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final ODatabaseDocumentInternal database = getDatabase(); + final StringBuilder word = new StringBuilder(); + + parserRequiredKeyword(KEYWORD_ALTER); + parserRequiredKeyword(KEYWORD_SEQUENCE); + this.sequenceName = parserRequiredWord(false, "Expected "); + this.params = new OSequence.CreateParams(); + + String temp; + while ((temp = parseOptionalWord(true)) != null) { + if (parserIsEnded()) { + break; + } + + if (temp.equals(KEYWORD_START)) { + String startAsString = parserRequiredWord(true, "Expected "); + this.params.start = Long.parseLong(startAsString); + } else if (temp.equals(KEYWORD_INCREMENT)) { + String incrementAsString = parserRequiredWord(true, "Expected "); + this.params.increment = Integer.parseInt(incrementAsString); + } else if (temp.equals(KEYWORD_CACHE)) { + String cacheAsString = parserRequiredWord(true, "Expected "); + this.params.cacheSize = Integer.parseInt(cacheAsString); + } + } + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + @Override + public Object execute(Map iArgs) { + if (this.sequenceName == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + final ODatabaseDocument database = getDatabase(); + OSequence sequence = database.getMetadata().getSequenceLibrary().getSequence(this.sequenceName); + + boolean result = sequence.updateParams(this.params); + sequence.reset(); + return result; + } + + @Override + public String getSyntax() { + return "ALTER SEQUENCE [START ] [INCREMENT ] [CACHE ]"; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateClass.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateClass.java new file mode 100755 index 00000000000..8dedd08cbf8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateClass.java @@ -0,0 +1,259 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.sql.parser.OCreateClassStatement; +import com.orientechnologies.orient.core.sql.parser.OIdentifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SQL CREATE CLASS command: Creates a new property in the target class. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLCreateClass extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_CLASS = "CLASS"; + public static final String KEYWORD_EXTENDS = "EXTENDS"; + public static final String KEYWORD_ABSTRACT = "ABSTRACT"; + public static final String KEYWORD_CLUSTER = "CLUSTER"; + public static final String KEYWORD_CLUSTERS = "CLUSTERS"; + public static final String KEYWORD_IF = "IF"; + public static final String KEYWORD_NOT = "NOT"; + public static final String KEYWORD_EXISTS = "EXISTS"; + + private String className; + private List superClasses = new ArrayList(); + private int[] clusterIds; + private Integer clusters = null; + private boolean ifNotExists = false; + + public OCommandExecutorSQLCreateClass parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocumentInternal database = getDatabase(); + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CREATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CREATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLASS)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLASS + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected ", parserText, oldPos); + + className = word.toString(); + if (this.preParsedStatement != null) { + className = ((OCreateClassStatement) preParsedStatement).name.getStringValue(); + } + if (className == null) + throw new OCommandSQLParsingException("Expected ", parserText, oldPos); + + oldPos = pos; + + while ((pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true)) > -1) { + final String k = word.toString(); + if (k.equals(KEYWORD_EXTENDS)) { + boolean hasNext; + boolean newParser = this.preParsedStatement != null; + OClass superClass; + do { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException( + "Syntax error after EXTENDS for class " + className + ". Expected the super-class name. Use " + getSyntax(), + parserText, oldPos); + String superclassName = decodeClassName(word.toString()); + + if (!database.getMetadata().getSchema().existsClass(superclassName) && !newParser) + throw new OCommandSQLParsingException("Super-class " + word + " not exists", parserText, oldPos); + superClass = database.getMetadata().getSchema().getClass(superclassName); + superClasses.add(superClass); + hasNext = false; + for (; pos < parserText.length(); pos++) { + char ch = parserText.charAt(pos); + if (ch == ',') + hasNext = true; + else if (Character.isLetterOrDigit(ch)) + break; + } + } while (hasNext); + if (newParser) { + OCreateClassStatement statement = (OCreateClassStatement) this.preParsedStatement; + List superclasses = statement.getSuperclasses(); + this.superClasses.clear(); + for (OIdentifier superclass : superclasses) { + String superclassName = superclass.getStringValue(); + if (!database.getMetadata().getSchema().existsClass(superclassName)) + throw new OCommandSQLParsingException("Super-class " + word + " not exists", parserText, oldPos); + superClass = database.getMetadata().getSchema().getClass(superclassName); + this.superClasses.add(superClass); + } + } + } else if (k.equals(KEYWORD_CLUSTER)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false, " =><()"); + if (pos == -1) + throw new OCommandSQLParsingException( + "Syntax error after CLUSTER for class " + className + ". Expected the cluster id or name. Use " + getSyntax(), + parserText, oldPos); + + final String[] clusterIdsAsStrings = word.toString().split(","); + if (clusterIdsAsStrings.length > 0) { + clusterIds = new int[clusterIdsAsStrings.length]; + for (int i = 0; i < clusterIdsAsStrings.length; ++i) { + if (Character.isDigit(clusterIdsAsStrings[i].charAt(0))) + // GET CLUSTER ID FROM NAME + clusterIds[i] = Integer.parseInt(clusterIdsAsStrings[i]); + else + // GET CLUSTER ID + clusterIds[i] = database.getStorage().getClusterIdByName(clusterIdsAsStrings[i]); + + if (clusterIds[i] == -1) + throw new OCommandSQLParsingException("Cluster with id " + clusterIds[i] + " does not exists", parserText, oldPos); + + try { + database.getStorage().getClusterById(clusterIds[i]); + } catch (Exception e) { + throw new OCommandSQLParsingException("Cluster with id " + clusterIds[i] + " does not exists", parserText, oldPos); + } + } + } + } else if (k.equals(KEYWORD_CLUSTERS)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false, " =><()"); + if (pos == -1) + throw new OCommandSQLParsingException( + "Syntax error after CLUSTERS for class " + className + ". Expected the number of clusters. Use " + getSyntax(), + parserText, oldPos); + + clusters = Integer.parseInt(word.toString()); + } else if (k.equals(KEYWORD_ABSTRACT)) { + clusterIds = new int[] { -1 }; + } else if (k.equals(KEYWORD_IF)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false, " =><()"); + if (!word.toString().equalsIgnoreCase(KEYWORD_NOT)) { + throw new OCommandSQLParsingException( + "Syntax error after IF for class " + className + ". Expected NOT. Use " + getSyntax(), parserText, oldPos); + } + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false, " =><()"); + if (!word.toString().equalsIgnoreCase(KEYWORD_EXISTS)) { + throw new OCommandSQLParsingException( + "Syntax error after IF NOT for class " + className + ". Expected EXISTS. Use " + getSyntax(), parserText, oldPos); + } + ifNotExists = true; + } else + throw new OCommandSQLParsingException("Invalid keyword: " + k); + + oldPos = pos; + } + + if (clusterIds == null) { + final int clusterId = database.getStorage().getClusterIdByName(className); + if (clusterId > -1) { + clusterIds = new int[] { clusterId }; + } + } + + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public boolean isDistributedExecutingOnLocalNodeFirst() { + return false; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + /** + * Execute the CREATE CLASS. + */ + public Object execute(final Map iArgs) { + if (className == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocument database = getDatabase(); + + boolean alreadyExists = database.getMetadata().getSchema().existsClass(className); + if (!alreadyExists || !ifNotExists) { + if (clusters != null) + database.getMetadata().getSchema().createClass(className, clusters, superClasses.toArray(new OClass[0])); + else + database.getMetadata().getSchema().createClass(className, clusterIds, superClasses.toArray(new OClass[0])); + } + return database.getMetadata().getSchema().getClasses().size(); + } + + @Override + public String getSyntax() { + return "CREATE CLASS [IF NOT EXISTS] [EXTENDS [,*] ] [CLUSTER *] [CLUSTERS ] [ABSTRACT]"; + } + + @Override + public String getUndoCommand() { + return "drop class " + className; + } + + @Override + public boolean involveSchema() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateCluster.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateCluster.java new file mode 100755 index 00000000000..588df5c754c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateCluster.java @@ -0,0 +1,141 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Map; + +/** + * SQL CREATE CLUSTER command: Creates a new cluster. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLCreateCluster extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_BLOB = "BLOB"; + public static final String KEYWORD_CLUSTER = "CLUSTER"; + public static final String KEYWORD_ID = "ID"; + + private String clusterName; + private int requestedId = -1; + private boolean blob = false; + + public OCommandExecutorSQLCreateCluster parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocumentInternal database = getDatabase(); + + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(KEYWORD_CREATE); + String nextWord = parserRequiredWord(true); + if (nextWord.equals("BLOB")) { + parserRequiredKeyword(KEYWORD_CLUSTER); + blob = true; + } else if (!nextWord.equals(KEYWORD_CLUSTER)) { + throw new OCommandSQLParsingException("Invalid Syntax: " + queryText); + } + + clusterName = parserRequiredWord(false); + clusterName = decodeClassName(clusterName); + if (!clusterName.isEmpty() && Character.isDigit(clusterName.charAt(0))) + throw new IllegalArgumentException("Cluster name cannot begin with a digit"); + + String temp = parseOptionalWord(true); + + while (temp != null) { + if (temp.equals(KEYWORD_ID)) { + requestedId = Integer.parseInt(parserRequiredWord(false)); + } + + temp = parseOptionalWord(true); + if (parserIsEnded()) + break; + } + + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + /** + * Execute the CREATE CLUSTER. + */ + public Object execute(final Map iArgs) { + if (clusterName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocument database = getDatabase(); + + final int clusterId = database.getClusterIdByName(clusterName); + if (clusterId > -1) + throw new OCommandSQLParsingException("Cluster '" + clusterName + "' already exists"); + + if (blob) { + if (requestedId == -1) { + return database.addBlobCluster(clusterName); + } else { + throw new OCommandExecutionException("Request id not supported by blob cluster creation."); + } + } else { + if (requestedId == -1) { + return database.addCluster(clusterName); + } else { + return database.addCluster(clusterName, requestedId, null); + } + } + } + + @Override + public String getUndoCommand() { + return "drop cluster " + clusterName; + } + + @Override + public String getSyntax() { + return "CREATE CLUSTER [ID ]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateFunction.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateFunction.java new file mode 100644 index 00000000000..7ab77605a59 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateFunction.java @@ -0,0 +1,127 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.function.OFunction; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SQL CREATE FUNCTION command. + * + * @author Luca Garulli + * @author Claudio Tesoriero + */ +public class OCommandExecutorSQLCreateFunction extends OCommandExecutorSQLAbstract { + public static final String NAME = "CREATE FUNCTION"; + private String name; + private String code; + private String language; + private boolean idempotent = false; + private List parameters = null; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLCreateFunction parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + parserRequiredKeyword("CREATE"); + parserRequiredKeyword("FUNCTION"); + + name = parserNextWord(false); + code = OIOUtils.getStringContent(parserNextWord(false)); + + String temp = parseOptionalWord(true); + while (temp != null) { + if (temp.equals("IDEMPOTENT")) { + parserNextWord(false); + idempotent = Boolean.parseBoolean(parserGetLastWord()); + } else if (temp.equals("LANGUAGE")) { + parserNextWord(false); + language = parserGetLastWord(); + } else if (temp.equals("PARAMETERS")) { + parserNextWord(false); + parameters = new ArrayList(); + OStringSerializerHelper.getCollection(parserGetLastWord(), 0, parameters); + if (parameters.size() == 0) + throw new OCommandExecutionException("Syntax Error. Missing function parameter(s): " + getSyntax()); + } + + temp = parserOptionalWord(true); + if (parserIsEnded()) + break; + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Execute the command and return the ODocument object created. + */ + public Object execute(final Map iArgs) { + if (name == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + if (name.isEmpty()) + throw new OCommandExecutionException("Syntax Error. You must specify a function name: " + getSyntax()); + if (code == null || code.isEmpty()) + throw new OCommandExecutionException("Syntax Error. You must specify the function code: " + getSyntax()); + + ODatabaseDocument database = getDatabase(); + final OFunction f = database.getMetadata().getFunctionLibrary().createFunction(name); + f.setCode(code); + f.setIdempotent(idempotent); + if (parameters != null) + f.setParameters(parameters); + if (language != null) + f.setLanguage(language); + f.save(); + return f.getId(); + } + + @Override + public String getSyntax() { + return "CREATE FUNCTION [PARAMETERS []] [IDEMPOTENT true|false] [LANGUAGE ]"; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateIndex.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateIndex.java new file mode 100755 index 00000000000..1d2bd67f656 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateIndex.java @@ -0,0 +1,338 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OPatternConst; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.*; + +/** + * SQL CREATE INDEX command: Create a new index against a property. + *

          + *

          + * Supports following grammar:
          + * "CREATE" "INDEX" <indexName> ["ON" <className> "(" <propName> ("," <propName>)* ")"] <indexType> + * [<keyType> ("," <keyType>)*] + *

          + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLCreateIndex extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_INDEX = "INDEX"; + public static final String KEYWORD_ON = "ON"; + public static final String KEYWORD_METADATA = "METADATA"; + public static final String KEYWORD_ENGINE = "ENGINE"; + + private String indexName; + private OClass oClass; + private String[] fields; + private OClass.INDEX_TYPE indexType; + private OType[] keyTypes; + private byte serializerKeyId; + private String engine; + private ODocument metadataDoc = null; + private String[] collates; + + public OCommandExecutorSQLCreateIndex parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CREATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CREATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_INDEX)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_INDEX + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected index name. Use " + getSyntax(), parserText, oldPos); + + indexName = decodeClassName(word.toString()); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Index type requested. Use " + getSyntax(), parserText, oldPos + 1); + + if (word.toString().equals(KEYWORD_ON)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Expected class name. Use " + getSyntax(), parserText, oldPos); + oldPos = pos; + oClass = findClass(decodeClassName(word.toString())); + + if (oClass == null) + throw new OCommandExecutionException("Class " + word + " not found"); + + pos = parserTextUpperCase.indexOf(")"); + if (pos == -1) { + throw new OCommandSQLParsingException("No right bracket found. Use " + getSyntax(), parserText, oldPos); + } + + final String props = parserText.substring(oldPos, pos).trim().substring(1); + + List propList = new ArrayList(); + Collections.addAll(propList, OPatternConst.PATTERN_COMMA_SEPARATED.split(props.trim())); + + fields = new String[propList.size()]; + propList.toArray(fields); + + for (int i = 0; i < fields.length; i++) { + final String fieldName = fields[i]; + + final int collatePos = fieldName.toUpperCase(Locale.ENGLISH).indexOf(" COLLATE "); + + if (collatePos > 0) { + if (collates == null) + collates = new String[fields.length]; + + collates[i] = fieldName.substring(collatePos + " COLLATE ".length()).toLowerCase(Locale.ENGLISH).trim(); + fields[i] = fieldName.substring(0, collatePos); + } else { + if (collates != null) + collates[i] = null; + } + fields[i] = decodeClassName(fields[i]); + } + + for (String propToIndex : fields) { + checkMapIndexSpecifier(propToIndex, parserText, oldPos); + + propList.add(propToIndex); + } + + oldPos = pos + 1; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Index type requested. Use " + getSyntax(), parserText, oldPos + 1); + } else { + if (indexName.indexOf('.') > 0) { + final String[] parts = indexName.split("\\."); + + oClass = findClass(parts[0]); + if (oClass == null) + throw new OCommandExecutionException("Class " + parts[0] + " not found"); + + fields = new String[] { parts[1] }; + } + } + + indexType = OClass.INDEX_TYPE.valueOf(word.toString()); + + if (indexType == null) + throw new OCommandSQLParsingException("Index type is null", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + + if (word.toString().equals(KEYWORD_ENGINE)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + oldPos = pos; + engine = word.toString().toUpperCase(Locale.ENGLISH); + } else + parserGoBack(); + + final int configPos = parserTextUpperCase.indexOf(KEYWORD_METADATA, oldPos); + + if (configPos > -1) { + final String configString = parserText.substring(configPos + KEYWORD_METADATA.length()).trim(); + metadataDoc = new ODocument().fromJSON(configString); + } + + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos != -1 && !word.toString().equalsIgnoreCase("NULL") && !word.toString().equalsIgnoreCase(KEYWORD_METADATA)) { + final String typesString; + if (configPos > -1) + typesString = parserTextUpperCase.substring(oldPos, configPos).trim(); + else + typesString = parserTextUpperCase.substring(oldPos).trim(); + + if (word.toString().equalsIgnoreCase("RUNTIME")) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + + serializerKeyId = Byte.parseByte(word.toString()); + } else { + ArrayList keyTypeList = new ArrayList(); + for (String typeName : OPatternConst.PATTERN_COMMA_SEPARATED.split(typesString)) { + keyTypeList.add(OType.valueOf(typeName)); + } + + keyTypes = new OType[keyTypeList.size()]; + keyTypeList.toArray(keyTypes); + + if (fields != null && fields.length != 0 && fields.length != keyTypes.length) { + throw new OCommandSQLParsingException( + "Count of fields does not match with count of property types. " + "Fields: " + Arrays.toString(fields) + "; Types: " + + Arrays.toString(keyTypes), parserText, oldPos); + } + } + } + + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the CREATE INDEX. + */ + @SuppressWarnings("rawtypes") + public Object execute(final Map iArgs) { + if (indexName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocument database = getDatabase(); + final OIndex idx; + List collatesList = null; + + if (collates != null) { + collatesList = new ArrayList(); + + for (String collate : collates) { + if (collate != null) { + final OCollate col = OSQLEngine.getCollate(collate); + collatesList.add(col); + } else + collatesList.add(null); + } + } + + if (fields == null || fields.length == 0) { + OIndexFactory factory = OIndexes.getFactory(indexType.toString(), null); + + if (keyTypes != null) + idx = database.getMetadata().getIndexManager().createIndex(indexName, indexType.toString(), + new OSimpleKeyIndexDefinition(keyTypes, collatesList, factory.getLastVersion()), null, null, metadataDoc, engine); + else if (serializerKeyId != 0) { + idx = database.getMetadata().getIndexManager() + .createIndex(indexName, indexType.toString(), new ORuntimeKeyIndexDefinition(serializerKeyId, factory.getLastVersion()), + null, null, metadataDoc, engine); + } else { + OLogManager.instance().warn(this, + "Key type is not provided for '%s' index. Untyped indexes are deprecated and considered unstable." + + " Please specify a key type.", indexName); + idx = database.getMetadata().getIndexManager() + .createIndex(indexName, indexType.toString(), null, null, null, metadataDoc, engine); + } + } else { + if ((keyTypes == null || keyTypes.length == 0) && collates == null) { + idx = oClass.createIndex(indexName, indexType.toString(), null, metadataDoc, engine, fields); + } else { + final List fieldTypeList; + if (keyTypes == null) { + for (final String fieldName : fields) { + if (!fieldName.equals("@rid") && !oClass.existsProperty(fieldName)) + throw new OIndexException( + "Index with name : '" + indexName + "' cannot be created on class : '" + oClass.getName() + "' because field: '" + + fieldName + "' is absent in class definition."); + } + fieldTypeList = ((OClassImpl) oClass).extractFieldTypes(fields); + } else + fieldTypeList = Arrays.asList(keyTypes); + + final OIndexDefinition idxDef = OIndexDefinitionFactory + .createIndexDefinition(oClass, Arrays.asList(fields), fieldTypeList, collatesList, indexType.toString(), null); + + idx = database.getMetadata().getIndexManager() + .createIndex(indexName, indexType.name(), idxDef, oClass.getPolymorphicClusterIds(), null, metadataDoc, engine); + } + } + + if (idx != null) + return idx.getSize(); + + return null; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + @Override + public String getSyntax() { + return "CREATE INDEX [ON (prop-names [COLLATE ])] [] [ENGINE ] [METADATA {JSON Index Metadata Document}]"; + } + + private OClass findClass(String part) { + return getDatabase().getMetadata().getSchema().getClass(part); + } + + private void checkMapIndexSpecifier(final String fieldName, final String text, final int pos) { + final String[] fieldNameParts = OPatternConst.PATTERN_SPACES.split(fieldName); + if (fieldNameParts.length == 1) + return; + + if (fieldNameParts.length == 3) { + if ("by".equals(fieldNameParts[1].toLowerCase(Locale.ENGLISH))) { + try { + OPropertyMapIndexDefinition.INDEX_BY.valueOf(fieldNameParts[2].toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException iae) { + throw new OCommandSQLParsingException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldName + "'", text, pos); + } + return; + } + throw new OCommandSQLParsingException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldName + "'", text, pos); + } + + throw new OCommandSQLParsingException( + "Illegal field name format, should be ' [by key|value]' but was '" + fieldName + "'", text, pos); + } + + @Override + public String getUndoCommand() { + return "drop index " + indexName; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateLink.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateLink.java new file mode 100755 index 00000000000..e288d79fdef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateLink.java @@ -0,0 +1,351 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyList; +import com.orientechnologies.orient.core.db.record.ORecordLazySet; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.intent.OIntentMassiveInsert; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * SQL CREATE LINK command: Transform a JOIN relationship to a physical LINK + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLCreateLink extends OCommandExecutorSQLAbstract { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_LINK = "LINK"; + private static final String KEYWORD_FROM = "FROM"; + private static final String KEYWORD_TO = "TO"; + private static final String KEYWORD_TYPE = "TYPE"; + + private String destClassName; + private String destField; + private String sourceClassName; + private String sourceField; + private String linkName; + private OType linkType; + private boolean inverse = false; + + public OCommandExecutorSQLCreateLink parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CREATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CREATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_LINK)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_LINK + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_FROM + " not found. Use " + getSyntax(), parserText, oldPos); + + if (!word.toString().equalsIgnoreCase(KEYWORD_FROM)) { + // GET THE LINK NAME + linkName = word.toString(); + + if (OStringSerializerHelper.contains(linkName, ' ')) + throw new OCommandSQLParsingException("Link name '" + linkName + "' contains not valid characters", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + } + + if (word.toString().equalsIgnoreCase(KEYWORD_TYPE)) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + + if (pos == -1) + throw new OCommandSQLParsingException("Link type missed. Use " + getSyntax(), parserText, oldPos); + + linkType = OType.valueOf(word.toString().toUpperCase(Locale.ENGLISH)); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + } + + if (pos == -1 || !word.toString().equals(KEYWORD_FROM)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_FROM + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + String[] parts = word.toString().split("\\."); + if (parts.length != 2) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + sourceClassName = parts[0]; + if (sourceClassName == null) + throw new OCommandSQLParsingException("Class not found", parserText, pos); + sourceField = parts[1]; + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_TO)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_TO + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + parts = word.toString().split("\\."); + if (parts.length != 2) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + destClassName = parts[0]; + if (destClassName == null) + throw new OCommandSQLParsingException("Class not found", parserText, pos); + destField = parts[1]; + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1) + return this; + + if (!word.toString().equalsIgnoreCase("INVERSE")) + throw new OCommandSQLParsingException("Missed 'INVERSE'. Use " + getSyntax(), parserText, pos); + + inverse = true; + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the CREATE LINK. + */ + public Object execute(final Map iArgs) { + if (destField == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocumentInternal database = getDatabase(); + if (!(database.getDatabaseOwner() instanceof ODatabaseDocument)) + throw new OCommandSQLParsingException("This command supports only the database type ODatabaseDocumentTx and type '" + + database.getClass() + "' was found"); + + final ODatabaseDocument db = (ODatabaseDocument) database.getDatabaseOwner(); + + final OClass sourceClass = database.getMetadata().getSchema().getClass(sourceClassName); + if (sourceClass == null) + throw new OCommandExecutionException("Source class '" + sourceClassName + "' not found"); + + final OClass destClass = database.getMetadata().getSchema().getClass(destClassName); + if (destClass == null) + throw new OCommandExecutionException("Destination class '" + destClassName + "' not found"); + + Object value; + + String cmd = "select from "; + if (!ODocumentHelper.ATTRIBUTE_RID.equals(destField)) { + cmd = "select from " + destClassName + " where " + destField + " = "; + } + + List result; + ODocument target; + Object oldValue; + long total = 0; + + if (linkName == null) + // NO LINK NAME EXPRESSED: OVERWRITE THE SOURCE FIELD + linkName = sourceField; + + boolean multipleRelationship; + if (linkType != null) + // DETERMINE BASED ON FORCED TYPE + multipleRelationship = linkType == OType.LINKSET || linkType == OType.LINKLIST; + else + multipleRelationship = false; + + long totRecords = db.countClass(sourceClass.getName()); + long currRecord = 0; + + if (progressListener != null) + progressListener.onBegin(this, totRecords, false); + + database.declareIntent(new OIntentMassiveInsert()); + try { + // BROWSE ALL THE RECORDS OF THE SOURCE CLASS + for (ODocument doc : db.browseClass(sourceClass.getName())) { + value = doc.field(sourceField); + + if (value != null) { + if (value instanceof ODocument || value instanceof ORID) { + // ALREADY CONVERTED + } else if (value instanceof Collection) { + // TODO + } else { + // SEARCH THE DESTINATION RECORD + target = null; + + if (!ODocumentHelper.ATTRIBUTE_RID.equals(destField) && value instanceof String) + if (((String) value).length() == 0) + value = null; + else + value = "'" + value + "'"; + + result = database.command(new OSQLSynchQuery(cmd + value)).execute(); + + if (result == null || result.size() == 0) + value = null; + else if (result.size() > 1) + throw new OCommandExecutionException("Cannot create link because multiple records was found in class '" + + destClass.getName() + "' with value " + value + " in field '" + destField + "'"); + else { + target = result.get(0); + value = target; + } + + if (target != null && inverse) { + // INVERSE RELATIONSHIP + oldValue = target.field(linkName); + + if (oldValue != null) { + if (!multipleRelationship) + multipleRelationship = true; + + Collection coll; + if (oldValue instanceof Collection) { + // ADD IT IN THE EXISTENT COLLECTION + coll = (Collection) oldValue; + target.setDirty(); + } else { + // CREATE A NEW COLLECTION FOR BOTH + coll = new ArrayList(2); + target.field(linkName, coll); + coll.add((ODocument) oldValue); + } + coll.add(doc); + } else { + if (linkType != null) + if (linkType == OType.LINKSET) { + value = new ORecordLazySet(target); + ((Set) value).add(doc); + } else if (linkType == OType.LINKLIST) { + value = new ORecordLazyList(target); + ((ORecordLazyList) value).add(doc); + } else + // IGNORE THE TYPE, SET IT AS LINK + value = doc; + else + value = doc; + + target.field(linkName, value); + } + target.save(); + + } else { + // SET THE REFERENCE + doc.field(linkName, value); + doc.save(); + } + + total++; + } + } + + if (progressListener != null) + progressListener.onProgress(this, currRecord, currRecord * 100f / totRecords); + } + + if (total > 0) { + if (inverse) { + // REMOVE THE OLD PROPERTY IF ANY + OProperty prop = destClass.getProperty(linkName); + if (prop != null) + destClass.dropProperty(linkName); + + if (linkType == null) + linkType = multipleRelationship ? OType.LINKSET : OType.LINK; + + // CREATE THE PROPERTY + destClass.createProperty(linkName, linkType, sourceClass); + + } else { + + // REMOVE THE OLD PROPERTY IF ANY + OProperty prop = sourceClass.getProperty(linkName); + if (prop != null) + sourceClass.dropProperty(linkName); + + // CREATE THE PROPERTY + sourceClass.createProperty(linkName, OType.LINK, destClass); + } + } + + if (progressListener != null) + progressListener.onCompletition(this, true); + + } catch (Exception e) { + if (progressListener != null) + progressListener.onCompletition(this, false); + + throw OException.wrapException(new OCommandExecutionException("Error on creation of links"), e); + + } finally { + database.declareIntent(null); + } + + return total; + } + + @Override + public String getSyntax() { + return "CREATE LINK [TYPE ] FROM . TO . [INVERSE]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateProperty.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateProperty.java new file mode 100644 index 00000000000..32bc2f581eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateProperty.java @@ -0,0 +1,404 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; +import com.orientechnologies.orient.core.metadata.schema.OPropertyImpl; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * SQL CREATE PROPERTY command: Creates a new property in the target class. + * + * @author Luca Garulli + * @author Michael MacFadden + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLCreateProperty extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_PROPERTY = "PROPERTY"; + + public static final String KEYWORD_MANDATORY = "MANDATORY"; + public static final String KEYWORD_READONLY = "READONLY"; + public static final String KEYWORD_NOTNULL = "NOTNULL"; + public static final String KEYWORD_MIN = "MIN"; + public static final String KEYWORD_MAX = "MAX"; + public static final String KEYWORD_DEFAULT = "DEFAULT"; + public static final String KEYWORD_COLLATE = "COLLATE"; + public static final String KEYWORD_REGEX = "REGEX"; + + private String className; + private String fieldName; + + private boolean ifNotExists = false; + + private OType type; + private String linked; + + private boolean readonly = false; + private boolean mandatory = false; + private boolean notnull = false; + + private String max = null; + private String min = null; + private String defaultValue = null; + private String collate = null; + private String regex = null; + + private boolean unsafe = false; + + public OCommandExecutorSQLCreateProperty parse(final OCommandRequest iRequest) { + + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CREATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CREATE + " not found", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_PROPERTY)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_PROPERTY + " not found", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected .", parserText, oldPos); + + String[] parts = split(word); + if (parts.length != 2) + throw new OCommandSQLParsingException("Expected .", parserText, oldPos); + + className = decodeClassName(parts[0]); + if (className == null) + throw new OCommandSQLParsingException("Class not found", parserText, oldPos); + fieldName = decodeClassName(parts[1]); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missed property type", parserText, oldPos); + if ("IF".equalsIgnoreCase(word.toString())) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missed property type", parserText, oldPos); + if (!"NOT".equalsIgnoreCase(word.toString())) { + throw new OCommandSQLParsingException("Expected NOT EXISTS after IF", parserText, oldPos); + } + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Missed property type", parserText, oldPos); + if (!"EXISTS".equalsIgnoreCase(word.toString())) { + throw new OCommandSQLParsingException("Expected EXISTS after IF NOT", parserText, oldPos); + } + this.ifNotExists = true; + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + } + + type = OType.valueOf(word.toString()); + + // Use a REGEX for the rest because we know exactly what we are looking for. + // If we are in strict mode, the parser took care of strict matching. + String rest = parserText.substring(pos).trim(); + String pattern = "(`[^`]*`|[^\\(]\\S*)?\\s*(\\(.*\\))?\\s*(UNSAFE)?"; + + Pattern r = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + Matcher m = r.matcher(rest.trim()); + + if (m.matches()) { + // Linked Type / Class + if (m.group(1) != null && !m.group(1).equalsIgnoreCase("UNSAFE")) { + linked = m.group(1); + if (linked.startsWith("`") && linked.endsWith("`") && linked.length() > 1) { + linked = linked.substring(1, linked.length() - 1); + } + } + + // Attributes + if (m.group(2) != null) { + String raw = m.group(2); + String atts = raw.substring(1, raw.length() - 1); + processAtts(atts); + } + + // UNSAFE + if (m.group(3) != null) { + this.unsafe = true; + } + } else { + // Syntax Error + } + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + private void processAtts(String atts) { + String[] split = atts.split(","); + for (String attDef : split) { + String[] parts = attDef.trim().split("\\s+"); + if (parts.length > 2) { + onInvalidAttributeDefinition(attDef); + } + + String att = parts[0].trim(); + if (att.equalsIgnoreCase(KEYWORD_MANDATORY)) { + this.mandatory = getOptionalBoolean(parts); + } else if (att.equalsIgnoreCase(KEYWORD_READONLY)) { + this.readonly = getOptionalBoolean(parts); + } else if (att.equalsIgnoreCase(KEYWORD_NOTNULL)) { + this.notnull = getOptionalBoolean(parts); + } else if (att.equalsIgnoreCase(KEYWORD_MIN)) { + this.min = getRequiredValue(attDef, parts); + } else if (att.equalsIgnoreCase(KEYWORD_MAX)) { + this.max = getRequiredValue(attDef, parts); + } else if (att.equalsIgnoreCase(KEYWORD_DEFAULT)) { + this.defaultValue = getRequiredValue(attDef, parts); + } else if (att.equalsIgnoreCase(KEYWORD_COLLATE)) { + this.collate = getRequiredValue(attDef, parts); + } else if (att.equalsIgnoreCase(KEYWORD_REGEX)) { + this.regex = getRequiredValue(attDef, parts); + } else { + onInvalidAttributeDefinition(attDef); + } + } + } + + private void onInvalidAttributeDefinition(String attDef) { + throw new OCommandSQLParsingException("Invalid attribute definition: '" + attDef + "'"); + } + + private boolean getOptionalBoolean(String[] parts) { + if (parts.length < 2) { + return true; + } + + String trimmed = parts[1].trim(); + if (trimmed.length() == 0) { + return true; + } + + return Boolean.parseBoolean(trimmed); + } + + private String getRequiredValue(String attDef, String[] parts) { + if (parts.length < 2) { + onInvalidAttributeDefinition(attDef); + } + + String value = parts[1].trim(); + if (value.length() == 0) { + onInvalidAttributeDefinition(attDef); + } + + if (value.equalsIgnoreCase("null")) { + value = null; + } + + if (value != null && isQuoted(value)) { + value = removeQuotes(value); + } + + return value; + } + + private String[] split(StringBuilder word) { + List result = new ArrayList(); + StringBuilder builder = new StringBuilder(); + boolean quoted = false; + for (char c : word.toString().toCharArray()) { + if (!quoted) { + if (c == '`') { + quoted = true; + } else if (c == '.') { + String nextToken = builder.toString().trim(); + if (nextToken.length() > 0) { + result.add(nextToken); + } + builder = new StringBuilder(); + } else { + builder.append(c); + } + } else { + if (c == '`') { + quoted = false; + } else { + builder.append(c); + } + } + } + String nextToken = builder.toString().trim(); + if (nextToken.length() > 0) { + result.add(nextToken); + } + return result.toArray(new String[] {}); + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Execute the CREATE PROPERTY. + */ + public Object execute(final Map iArgs) { + if (type == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocument database = getDatabase(); + final OClassImpl sourceClass = (OClassImpl) database.getMetadata().getSchema().getClass(className); + if (sourceClass == null) + throw new OCommandExecutionException("Source class '" + className + "' not found"); + + OPropertyImpl prop = (OPropertyImpl) sourceClass.getProperty(fieldName); + + if (prop != null) { + if (ifNotExists) { + return sourceClass.properties().size(); + } + throw new OCommandExecutionException( + "Property '" + className + "." + fieldName + "' already exists. Remove it before to retry."); + } + + // CREATE THE PROPERTY + OClass linkedClass = null; + OType linkedType = null; + if (linked != null) { + // FIRST SEARCH BETWEEN CLASSES + linkedClass = database.getMetadata().getSchema().getClass(linked); + + if (linkedClass == null) + // NOT FOUND: SEARCH BETWEEN TYPES + linkedType = OType.valueOf(linked.toUpperCase(Locale.ENGLISH)); + } + + // CREATE IT LOCALLY + OPropertyImpl internalProp = sourceClass.addPropertyInternal(fieldName, type, linkedType, linkedClass, unsafe); + boolean toSave = false; + if (readonly) { + internalProp.setReadonly(true); + toSave = true; + } + + if (mandatory) { + internalProp.setMandatory(true); + toSave = true; + } + + if (notnull) { + internalProp.setNotNull(true); + toSave = true; + } + + if (max != null) { + internalProp.setMax(max); + toSave = true; + } + + if (min != null) { + internalProp.setMin(min); + toSave = true; + } + + if (defaultValue != null) { + internalProp.setDefaultValue(defaultValue); + toSave = true; + } + + if (collate != null) { + internalProp.setCollate(collate); + toSave = true; + } + + if (regex != null) { + internalProp.setRegexp(regex); + toSave = true; + } + + if (toSave) { + internalProp.save(); + } + + return sourceClass.properties().size(); + } + + private String removeQuotes(String s) { + s = s.trim(); + return s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\""); + } + + private boolean isQuoted(String s) { + s = s.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) + return true; + if (s.startsWith("'") && s.endsWith("'")) + return true; + if (s.startsWith("`") && s.endsWith("`")) + return true; + + return false; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + @Override + public String getUndoCommand() { + return "drop property " + className + "." + fieldName; + } + + @Override + public String getSyntax() { + return "CREATE PROPERTY . [IF NOT EXISTS] [|] " + + "[(mandatory , notnull , , default , min , max )] " + "[UNSAFE]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateSequence.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateSequence.java new file mode 100644 index 00000000000..71f8bb895bb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateSequence.java @@ -0,0 +1,106 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE; +import com.orientechnologies.orient.core.metadata.sequence.OSequenceHelper; + +import java.util.Arrays; +import java.util.Map; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 2/28/2015 + */ +public class OCommandExecutorSQLCreateSequence extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_SEQUENCE = "SEQUENCE"; + public static final String KEYWORD_TYPE = "TYPE"; + public static final String KEYWORD_START = "START"; + public static final String KEYWORD_INCREMENT = "INCREMENT"; + public static final String KEYWORD_CACHE = "CACHE"; + + private String sequenceName; + private SEQUENCE_TYPE sequenceType; + private OSequence.CreateParams params; + + @Override + public OCommandExecutorSQLCreateSequence parse(OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(KEYWORD_CREATE); + parserRequiredKeyword(KEYWORD_SEQUENCE); + this.sequenceName = parserRequiredWord(false, "Expected "); + this.params = new OSequence.CreateParams().setDefaults(); + + String temp; + while ((temp = parseOptionalWord(true)) != null) { + if (parserIsEnded()) { + break; + } + + if (temp.equals(KEYWORD_TYPE)) { + String typeAsString = parserRequiredWord(true, "Expected "); + try { + this.sequenceType = OSequenceHelper.getSequenceTyeFromString(typeAsString); + } catch (IllegalArgumentException e) { + throw new OCommandSQLParsingException( + "Unknown sequence type '" + typeAsString + "'. Supported attributes are: " + Arrays + .toString(SEQUENCE_TYPE.values())); + } + } else if (temp.equals(KEYWORD_START)) { + String startAsString = parserRequiredWord(true, "Expected "); + this.params.start = Long.parseLong(startAsString); + } else if (temp.equals(KEYWORD_INCREMENT)) { + String incrementAsString = parserRequiredWord(true, "Expected "); + this.params.increment = Integer.parseInt(incrementAsString); + } else if (temp.equals(KEYWORD_CACHE)) { + String cacheAsString = parserRequiredWord(true, "Expected "); + this.params.cacheSize = Integer.parseInt(cacheAsString); + } + } + + if (this.sequenceType == null) { + this.sequenceType = OSequenceHelper.DEFAULT_SEQUENCE_TYPE; + } + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + @Override + public Object execute(Map iArgs) { + if (this.sequenceName == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + final ODatabaseDocument database = getDatabase(); + + database.getMetadata().getSequenceLibrary().createSequence(this.sequenceName, this.sequenceType, this.params); + + return database.getMetadata().getSequenceLibrary().getSequenceCount(); + } + + @Override + public String getSyntax() { + return "CREATE SEQUENCE [TYPE ] [START ] [INCREMENT ] [CACHE ]"; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateUser.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateUser.java new file mode 100644 index 00000000000..ca26cbff051 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLCreateUser.java @@ -0,0 +1,162 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Creates a new user. + * + * @author Matan Shukry (matanshukry@gmail.com) + * @since 4/22/2015 + */ +public class OCommandExecutorSQLCreateUser extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_CREATE = "CREATE"; + public static final String KEYWORD_USER = "USER"; + public static final String KEYWORD_IDENTIFIED = "IDENTIFIED"; + public static final String KEYWORD_BY = "BY"; + public static final String KEYWORD_ROLE = "ROLE"; + public static final String SYNTAX = "CREATE USER IDENTIFIED BY [ ROLE ]"; + + private static final String USER_CLASS = "OUser"; + private static final String USER_FIELD_NAME = "name"; + private static final String USER_FIELD_PASSWORD = "password"; + private static final String USER_FIELD_STATUS = "status"; + private static final String USER_FIELD_ROLES = "roles"; + + private static final String DEFAULT_STATUS = "ACTIVE"; + private static final String DEFAULT_ROLE = "writer"; + private static final String ROLE_CLASS = "ORole"; + private static final String ROLE_FIELD_NAME = "name"; + + private String userName; + private String pass; + private List roles; + + @Override + public OCommandExecutorSQLCreateUser parse(OCommandRequest iRequest) { + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(KEYWORD_CREATE); + parserRequiredKeyword(KEYWORD_USER); + this.userName = parserRequiredWord(false, "Expected "); + + parserRequiredKeyword(KEYWORD_IDENTIFIED); + parserRequiredKeyword(KEYWORD_BY); + this.pass = parserRequiredWord(false, "Expected "); + + this.roles = new ArrayList(); + + String temp; + while ((temp = parseOptionalWord(true)) != null) { + if (parserIsEnded()) { + break; + } + + if (temp.equals(KEYWORD_ROLE)) { + String role = parserRequiredWord(false, "Expected "); + int roleLen = (role != null) ? role.length() : 0; + if (roleLen > 0) { + if (role.charAt(0) == '[' && role.charAt(roleLen - 1) == ']') { + role = role.substring(1, role.length() - 1); + String[] splits = role.split("[, ]"); + for (String spl : splits) { + if (spl.length() > 0) { + this.roles.add(spl); + } + } + } else { + this.roles.add(role); + } + } + } + } + + return this; + } + + @Override + public Object execute(Map iArgs) { + if (this.userName == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + // Build following command: + // INSERT INTO OUser SET name='', password='', status='ACTIVE', + // role=(SELECT FROM ORole WHERE name in ['', '', ...]) + + // INSERT INTO OUser SET + StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO "); + sb.append(USER_CLASS); + sb.append(" SET "); + + // name= + sb.append(USER_FIELD_NAME); + sb.append("='"); + sb.append(this.userName); + sb.append("'"); + + // pass= + sb.append(','); + sb.append(USER_FIELD_PASSWORD); + sb.append("='"); + sb.append(this.pass); + sb.append("'"); + + // status=ACTIVE + sb.append(','); + sb.append(USER_FIELD_STATUS); + sb.append("='"); + sb.append(DEFAULT_STATUS); + sb.append("'"); + + // role=(select from ORole where name in [)] + if (this.roles.size() == 0) { + this.roles.add(DEFAULT_ROLE); + } + + sb.append(','); + sb.append(USER_FIELD_ROLES); + sb.append("=(SELECT FROM "); + sb.append(ROLE_CLASS); + sb.append(" WHERE "); + sb.append(ROLE_FIELD_NAME); + sb.append(" IN ["); + for (int i = 0; i < this.roles.size(); ++i) { + if (i > 0) { + sb.append(", "); + } + String role = roles.get(i); + if (role.startsWith("'") || role.startsWith("\"")) { + sb.append(this.roles.get(i)); + } else { + sb.append("'"); + sb.append(this.roles.get(i)); + sb.append("'"); + } + } + sb.append("])"); + return getDatabase().command(new OCommandSQL(sb.toString())).execute(); + } + + @Override + public String getSyntax() { + return SYNTAX; + } + + @Override + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.LOCAL; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelegate.java new file mode 100644 index 00000000000..e2b8d0fd61a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelegate.java @@ -0,0 +1,121 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.command.OCommandExecutorNotFoundException; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Map; +import java.util.Set; + +/** + * SQL UPDATE command. + * + * @author Luca Garulli + * + */ +public class OCommandExecutorSQLDelegate extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + protected OCommandExecutor delegate; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLDelegate parse(final OCommandRequest iCommand) { + if (iCommand instanceof OCommandRequestText) { + final OCommandRequestText textRequest = (OCommandRequestText) iCommand; + final String text = textRequest.getText(); + if (text == null) + throw new IllegalArgumentException("Command text is null"); + + final String textUpperCase = upperCase(text); + + delegate = OSQLEngine.getInstance().getCommand(textUpperCase); + if (delegate == null) + throw new OCommandExecutorNotFoundException("Cannot find a command executor for the command request: " + iCommand); + + delegate.setContext(context); + delegate.setLimit(iCommand.getLimit()); + delegate.parse(iCommand); + delegate.setProgressListener(progressListener); + if (delegate.getFetchPlan() != null) + textRequest.setFetchPlan(delegate.getFetchPlan()); + + } else + throw new OCommandExecutionException("Cannot find a command executor for the command request: " + iCommand); + return this; + } + + @Override + public long getDistributedTimeout() { + return delegate.getDistributedTimeout(); + } + + public Object execute(final Map iArgs) { + return delegate.execute(iArgs); + } + + @Override + public OCommandContext getContext() { + return delegate.getContext(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + public String getSyntax() { + return delegate.getSyntax(); + } + + @Override + public String getFetchPlan() { + return delegate.getFetchPlan(); + } + + @Override + public boolean isIdempotent() { + return delegate.isIdempotent(); + } + + public OCommandExecutor getDelegate() { + return delegate; + } + + @Override + public boolean isCacheable() { + return delegate.isCacheable(); + } + + @Override + public QUORUM_TYPE getQuorumType() { + if (delegate instanceof OCommandDistributedReplicateRequest) + return ((OCommandDistributedReplicateRequest) delegate).getQuorumType(); + return QUORUM_TYPE.ALL; + } + + @Override + public Set getInvolvedClusters() { + return delegate.getInvolvedClusters(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelete.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelete.java new file mode 100755 index 00000000000..3e56c9aaba8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDelete.java @@ -0,0 +1,407 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.parser.OStringParser; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.command.OCommandResultListener; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordAbstract; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilter; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.parser.ODeleteStatement; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLQuery; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SQL UPDATE command. + * + * @author Luca Garulli + * + */ +public class OCommandExecutorSQLDelete extends OCommandExecutorSQLAbstract + implements OCommandDistributedReplicateRequest, OCommandResultListener { + public static final String NAME = "DELETE FROM"; + public static final String KEYWORD_DELETE = "DELETE"; + private static final String VALUE_NOT_FOUND = "_not_found_"; + + private OSQLQuery query; + private String indexName = null; + private int recordCount = 0; + private String lockStrategy = "NONE"; + private String returning = "COUNT"; + private List allDeletedRecords; + + private OSQLFilter compiledFilter; + private boolean unsafe = false; + + public OCommandExecutorSQLDelete() { + } + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLDelete parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + query = null; + recordCount = 0; + + if (parserTextUpperCase.endsWith(KEYWORD_UNSAFE)) { + unsafe = true; + parserText = parserText.substring(0, parserText.length() - KEYWORD_UNSAFE.length() - 1); + parserTextUpperCase = parserTextUpperCase.substring(0, parserTextUpperCase.length() - KEYWORD_UNSAFE.length() - 1); + } + + parserRequiredKeyword(OCommandExecutorSQLDelete.KEYWORD_DELETE); + parserRequiredKeyword(OCommandExecutorSQLDelete.KEYWORD_FROM); + + String subjectName = parserRequiredWord(false, "Syntax error", " =><,\r\n", true); + if (subjectName == null) + throwSyntaxErrorException("Invalid subject name. Expected cluster, class, index or sub-query"); + + if (OStringParser.startsWithIgnoreCase(subjectName, OCommandExecutorSQLAbstract.INDEX_PREFIX)) { + // INDEX + indexName = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_PREFIX.length()); + + if (!parserIsEnded()) { + while (!parserIsEnded()) { + final String word = parserGetLastWord(); + + if (word.equals(KEYWORD_LOCK)) + lockStrategy = parseLock(); + else if (word.equals(KEYWORD_RETURN)) + returning = parseReturn(); + else if (word.equals(KEYWORD_UNSAFE)) + unsafe = true; + else if (word.equalsIgnoreCase(KEYWORD_WHERE)) + compiledFilter = OSQLEngine.getInstance().parseCondition(parserText.substring(parserGetCurrentPosition()), + getContext(), KEYWORD_WHERE); + + parserNextWord(true); + } + + } else + parserSetCurrentPosition(-1); + + } else if (subjectName.startsWith("(")) { + subjectName = subjectName.trim(); + query = database.command(new OSQLAsynchQuery(subjectName.substring(1, subjectName.length() - 1), this)); + parserNextWord(true); + if (!parserIsEnded()) { + while (!parserIsEnded()) { + final String word = parserGetLastWord(); + + if (word.equals(KEYWORD_LOCK)) + lockStrategy = parseLock(); + else if (word.equals(KEYWORD_RETURN)) + returning = parseReturn(); + else if (word.equals(KEYWORD_UNSAFE)) + unsafe = true; + else if (word.equalsIgnoreCase(KEYWORD_WHERE)) + compiledFilter = OSQLEngine.getInstance().parseCondition(parserText.substring(parserGetCurrentPosition()), + getContext(), KEYWORD_WHERE); + + parserNextWord(true); + } + } + } else { + parserNextWord(true); + + while (!parserIsEnded()) { + final String word = parserGetLastWord(); + + if (word.equals(KEYWORD_LOCK)) + lockStrategy = parseLock(); + else if (word.equals(KEYWORD_RETURN)) + returning = parseReturn(); + else { + parserGoBack(); + break; + } + + parserNextWord(true); + } + + final String condition = parserGetCurrentPosition() > -1 ? " " + parserText.substring(parserGetCurrentPosition()) : ""; + query = database.command(new OSQLAsynchQuery("select from " + getSelectTarget(subjectName) + condition, this)); + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + private String getSelectTarget(String subjectName) { + if (preParsedStatement == null) { + return subjectName; + } + return ((ODeleteStatement) preParsedStatement).fromClause.toString(); + } + + public Object execute(final Map iArgs) { + if (query == null && indexName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + if (!returning.equalsIgnoreCase("COUNT")) + allDeletedRecords = new ArrayList(); + + if (query != null) { + // AGAINST CLUSTERS AND CLASSES + query.setContext(getContext()); + + Object prevLockValue = query.getContext().getVariable("$locking"); + + if (lockStrategy.equals("RECORD")) + query.getContext().setVariable("$locking", OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK); + + query.execute(iArgs); + + query.getContext().setVariable("$locking", prevLockValue); + + if (returning.equalsIgnoreCase("COUNT")) + // RETURNS ONLY THE COUNT + return recordCount; + else + // RETURNS ALL THE DELETED RECORDS + return allDeletedRecords; + + } else { + // AGAINST INDEXES + if (compiledFilter != null) + compiledFilter.bindParameters(iArgs); + + final OIndex index = getDatabase().getMetadata().getIndexManager().getIndex(indexName); + if (index == null) + throw new OCommandExecutionException("Target index '" + indexName + "' not found"); + + Object key = null; + Object value = VALUE_NOT_FOUND; + + if (compiledFilter == null || compiledFilter.getRootCondition() == null) { + if (returning.equalsIgnoreCase("COUNT")) { + // RETURNS ONLY THE COUNT + final long total = index.getSize(); + index.clear(); + return total; + } else { + // RETURNS ALL THE DELETED RECORDS + OIndexCursor cursor = index.cursor(); + Map.Entry entry; + + while ((entry = cursor.nextEntry()) != null) { + OIdentifiable rec = entry.getValue(); + rec = rec.getRecord(); + if (rec != null) + allDeletedRecords.add((ORecord) rec); + } + + index.clear(); + + return allDeletedRecords; + } + + } else { + if (KEYWORD_KEY.equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString())) + // FOUND KEY ONLY + key = getIndexKey(index.getDefinition(), compiledFilter.getRootCondition().getRight()); + + else if (KEYWORD_RID.equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString())) { + // BY RID + value = OSQLHelper.getValue(compiledFilter.getRootCondition().getRight()); + + } else if (compiledFilter.getRootCondition().getLeft() instanceof OSQLFilterCondition) { + // KEY AND VALUE + final OSQLFilterCondition leftCondition = (OSQLFilterCondition) compiledFilter.getRootCondition().getLeft(); + if (KEYWORD_KEY.equalsIgnoreCase(leftCondition.getLeft().toString())) + key = getIndexKey(index.getDefinition(), leftCondition.getRight()); + + final OSQLFilterCondition rightCondition = (OSQLFilterCondition) compiledFilter.getRootCondition().getRight(); + if (KEYWORD_RID.equalsIgnoreCase(rightCondition.getLeft().toString())) + value = OSQLHelper.getValue(rightCondition.getRight()); + + } + + final boolean result; + if (value != VALUE_NOT_FOUND) { + assert key != null; + result = index.remove(key, (OIdentifiable) value); + } else + result = index.remove(key); + + if (returning.equalsIgnoreCase("COUNT")) + return result ? 1 : 0; + else + // TODO: REFACTOR INDEX TO RETURN DELETED ITEMS + throw new UnsupportedOperationException(); + } + } + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Deletes the current record. + */ + public boolean result(final Object iRecord) { + final ORecordAbstract record = ((OIdentifiable) iRecord).getRecord(); + + if (record instanceof ODocument && compiledFilter != null + && !Boolean.TRUE.equals(this.compiledFilter.evaluate(record, (ODocument) record, getContext()))) { + return true; + } + try { + if (record.getIdentity().isValid()) { + if (returning.equalsIgnoreCase("BEFORE")) + allDeletedRecords.add(record); + + // RESET VERSION TO DISABLE MVCC AVOIDING THE CONCURRENT EXCEPTION IF LOCAL CACHE IS NOT UPDATED + ORecordInternal.setVersion(record, -1); + + if (!unsafe && record instanceof ODocument) { + // CHECK IF ARE VERTICES OR EDGES + final OClass cls = ((ODocument) record).getSchemaClass(); + if (cls != null) { + if (cls.isSubClassOf("V")) + // FOUND VERTEX + throw new OCommandExecutionException( + "'DELETE' command cannot delete vertices. Use 'DELETE VERTEX' command instead, or apply the 'UNSAFE' keyword to force it"); + else if (cls.isSubClassOf("E")) + // FOUND EDGE + throw new OCommandExecutionException( + "'DELETE' command cannot delete edges. Use 'DELETE EDGE' command instead, or apply the 'UNSAFE' keyword to force it"); + } + } + + record.delete(); + + recordCount++; + return true; + } + return false; + } finally { + if (lockStrategy.equalsIgnoreCase("RECORD")) + ((OAbstractPaginatedStorage) getDatabase().getStorage()).releaseWriteLock(record.getIdentity()); + } + } + + public String getSyntax() { + return "DELETE FROM |RID|cluster: [UNSAFE] [LOCK ] [RETURN ] [WHERE *]"; + } + + @Override + public void end() { + } + + @Override + public int getSecurityOperationType() { + return ORole.PERMISSION_DELETE; + } + + /** + * Parses the returning keyword if found. + */ + protected String parseReturn() throws OCommandSQLParsingException { + final String returning = parserNextWord(true); + + if (!returning.equalsIgnoreCase("COUNT") && !returning.equalsIgnoreCase("BEFORE")) + throwParsingException("Invalid " + KEYWORD_RETURN + " value set to '" + returning + + "' but it should be COUNT (default), BEFORE. Example: " + KEYWORD_RETURN + " BEFORE"); + + return returning; + } + + private Object getIndexKey(final OIndexDefinition indexDefinition, Object value) { + if (indexDefinition instanceof OCompositeIndexDefinition) { + if (value instanceof List) { + final List values = (List) value; + List keyParams = new ArrayList(values.size()); + + for (Object o : values) { + keyParams.add(OSQLHelper.getValue(o)); + } + return indexDefinition.createValue(keyParams); + } else { + value = OSQLHelper.getValue(value); + if (value instanceof OCompositeKey) { + return value; + } else { + return indexDefinition.createValue(value); + } + } + } else { + return OSQLHelper.getValue(value); + } + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } + + @Override + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.LOCAL; + // ALWAYS EXECUTE THE COMMAND LOCALLY BECAUSE THERE IS NO A DISTRIBUTED UNDO WITH SHARDING + // + // return (indexName != null || query != null) && !getDatabase().getTransaction().isActive() ? + // DISTRIBUTED_EXECUTION_MODE.REPLICATE + // : DISTRIBUTED_EXECUTION_MODE.LOCAL; + } + + public DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() { + return DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS; + // return getDistributedExecutionMode() == DISTRIBUTED_EXECUTION_MODE.LOCAL ? DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS + // : DISTRIBUTED_RESULT_MGMT.MERGE; + } + + @Override + public Object getResult() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropClass.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropClass.java new file mode 100755 index 00000000000..1d571f76b9d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropClass.java @@ -0,0 +1,177 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.sql.parser.ODropClassStatement; + +import java.util.Map; + +/** + * SQL DROP CLASS command: Drops a class from the database. Cluster associated are removed too if are used exclusively by the + * deleting class. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLDropClass extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_CLASS = "CLASS"; + public static final String KEYWORD_UNSAFE = "UNSAFE"; + + private String className; + private boolean unsafe; + private boolean ifExists = false; + + public OCommandExecutorSQLDropClass parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + final boolean strict = getDatabase().getStorage().getConfiguration().isStrictSql(); + if (strict) { + this.className = ((ODropClassStatement) this.preParsedStatement).name.getStringValue(); + this.unsafe = ((ODropClassStatement) this.preParsedStatement).unsafe; + this.ifExists = ((ODropClassStatement) this.preParsedStatement).ifExists; + } else { + oldParsing((OCommandRequestText) iRequest); + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + private void oldParsing(OCommandRequestText iRequest) { + init(iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DROP)) { + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DROP + " not found. Use " + getSyntax(), parserText, oldPos); + } + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLASS)) { + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLASS + " not found. Use " + getSyntax(), parserText, oldPos); + } + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) { + throw new OCommandSQLParsingException("Expected . Use " + getSyntax(), parserText, pos); + } + + className = word.toString(); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos > -1 && KEYWORD_UNSAFE.equalsIgnoreCase(word.toString())) { + unsafe = true; + } + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + @Override + public long getDistributedTimeout() { + final OClass cls = getDatabase().getMetadata().getSchema().getClass(className); + if (className != null && cls != null) + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong() + (2 * cls.count()); + + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Execute the DROP CLASS. + */ + public Object execute(final Map iArgs) { + if (className == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + final ODatabaseDocument database = getDatabase(); + if (ifExists && !database.getMetadata().getSchema().existsClass(className)) { + return true; + } + final OClass cls = database.getMetadata().getSchema().getClass(className); + if (cls == null) { + return null; + } + + final long records = cls.count(true); + + if (records > 0 && !unsafe) { + // NOT EMPTY, CHECK IF CLASS IS OF VERTEX OR EDGES + if (cls.isSubClassOf("V")) { + // FOUND VERTEX CLASS + throw new OCommandExecutionException("'DROP CLASS' command cannot drop class '" + className + + "' because it contains Vertices. Use 'DELETE VERTEX' command first to avoid broken edges in a database, or apply the 'UNSAFE' keyword to force it"); + } else if (cls.isSubClassOf("E")) { + // FOUND EDGE CLASS + throw new OCommandExecutionException("'DROP CLASS' command cannot drop class '" + className + + "' because it contains Edges. Use 'DELETE EDGE' command first to avoid broken vertices in a database, or apply the 'UNSAFE' keyword to force it"); + } + } + + database.getMetadata().getSchema().dropClass(className); + + if (records > 0 && unsafe) { + // NOT EMPTY, CHECK IF CLASS IS OF VERTEX OR EDGES + if (cls.isSubClassOf("V")) { + // FOUND VERTICES + if (unsafe) + OLogManager.instance().warn(this, + "Dropped class '%s' containing %d vertices using UNSAFE mode. Database could contain broken edges", className, + records); + } else if (cls.isSubClassOf("E")) { + // FOUND EDGES + OLogManager.instance().warn(this, + "Dropped class '%s' containing %d edges using UNSAFE mode. Database could contain broken vertices", className, records); + } + } + + return true; + } + + @Override + public String getSyntax() { + return "DROP CLASS [IF EXISTS] [UNSAFE]"; + } + + @Override + public boolean involveSchema() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropCluster.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropCluster.java new file mode 100644 index 00000000000..50375533017 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropCluster.java @@ -0,0 +1,137 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.Map; + +/** + * SQL DROP CLUSTER command: Drop a cluster from the database + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLDropCluster extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_CLUSTER = "CLUSTER"; + + private String clusterName; + + public OCommandExecutorSQLDropCluster parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DROP)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DROP + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLUSTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLUSTER + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected . Use " + getSyntax(), parserText, pos); + + clusterName = word.toString(); + if (clusterName == null) + throw new OCommandSQLParsingException("Cluster is null. Use " + getSyntax(), parserText, pos); + + clusterName = decodeClassName(clusterName); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the DROP CLUSTER. + */ + public Object execute(final Map iArgs) { + if (clusterName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocumentInternal database = getDatabase(); + + // CHECK IF ANY CLASS IS USING IT + final int clusterId = database.getStorage().getClusterIdByName(clusterName); + for (OClass iClass : database.getMetadata().getSchema().getClasses()) { + for (int i : iClass.getClusterIds()) { + if (i == clusterId) + // IN USE + return false; + } + } + + // REMOVE CACHE OF COMMAND RESULTS IF ACTIVE + getDatabase().getMetadata().getCommandCache().invalidateResultsOfCluster(clusterName); + + database.dropCluster(clusterId, true); + return true; + } + + @Override + public long getDistributedTimeout() { + if (clusterName != null && getDatabase().existsCluster(clusterName)) + return 10 * getDatabase().countClusterElements(clusterName); + + return OGlobalConfiguration.DISTRIBUTED_COMMAND_LONG_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + protected boolean isClusterDeletable(int clusterId) { + final ODatabaseDocument database = getDatabase(); + for (OClass iClass : database.getMetadata().getSchema().getClasses()) { + for (int i : iClass.getClusterIds()) { + if (i == clusterId) + return false; + } + } + return true; + } + + @Override + public String getSyntax() { + return "DROP CLUSTER "; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropIndex.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropIndex.java new file mode 100644 index 00000000000..691d4d65ca1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropIndex.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.index.OIndex; + +import java.util.Map; + +/** + * SQL REMOVE INDEX command: Remove an index + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLDropIndex extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_INDEX = "INDEX"; + + private String name; + + public OCommandExecutorSQLDropIndex parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DROP)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DROP + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_INDEX)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_INDEX + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected index name. Use " + getSyntax(), parserText, oldPos); + + name = word.toString(); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the REMOVE INDEX. + */ + public Object execute(final Map iArgs) { + if (name == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + if (name.equals("*")) { + long totalIndexed = 0; + for (OIndex idx : getDatabase().getMetadata().getIndexManager().getIndexes()) { + getDatabase().getMetadata().getIndexManager().dropIndex(idx.getName()); + totalIndexed++; + } + + return totalIndexed; + + } else + getDatabase().getMetadata().getIndexManager().dropIndex(name); + + return 1; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public String getSyntax() { + return "DROP INDEX |.|*"; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropProperty.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropProperty.java new file mode 100644 index 00000000000..a2083638d3a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropProperty.java @@ -0,0 +1,188 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.comparator.OCaseInsentiveComparator; +import com.orientechnologies.common.util.OCollections; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.schema.OClassImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SQL CREATE PROPERTY command: Creates a new property in the target class. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLDropProperty extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_PROPERTY = "PROPERTY"; + + private String className; + private String fieldName; + private boolean ifExists; + private boolean force = false; + + public OCommandExecutorSQLDropProperty parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DROP)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DROP + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_PROPERTY)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_PROPERTY + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + String[] parts = word.toString().split("\\."); + if (parts.length != 2) + throw new OCommandSQLParsingException("Expected .. Use " + getSyntax(), parserText, pos); + + className = decodeClassName(parts[0]); + if (className == null) + throw new OCommandSQLParsingException("Class not found", parserText, pos); + fieldName = decodeClassName(parts[1]); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if (pos != -1) { + final String forceParameter = word.toString(); + if ("FORCE".equals(forceParameter)) { + force = true; + } else if ("IF".equals(word.toString())) { + pos = nextWord(parserText, parserTextUpperCase, pos, word, false); + if ("EXISTS".equals(word.toString())) { + this.ifExists = true; + }else{ + throw new OCommandSQLParsingException("Wrong query parameter, expecting EXISTS after IF", parserText, pos); + } + } else { + throw new OCommandSQLParsingException("Wrong query parameter", parserText, pos); + } + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the CREATE PROPERTY. + */ + public Object execute(final Map iArgs) { + if (fieldName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not yet been parsed"); + + final ODatabaseDocument database = getDatabase(); + final OClassImpl sourceClass = (OClassImpl) database.getMetadata().getSchema().getClass(className); + if (sourceClass == null) + throw new OCommandExecutionException("Source class '" + className + "' not found"); + + if(ifExists && !sourceClass.existsProperty(fieldName)){ + return null; + } + + final List> indexes = relatedIndexes(fieldName); + if (!indexes.isEmpty()) { + if (force) { + dropRelatedIndexes(indexes); + } else { + final StringBuilder indexNames = new StringBuilder(); + + boolean first = true; + for (final OIndex index : sourceClass.getClassInvolvedIndexes(fieldName)) { + if (!first) { + indexNames.append(", "); + } else { + first = false; + } + indexNames.append(index.getName()); + } + + throw new OCommandExecutionException("Property used in indexes (" + indexNames.toString() + + "). Please drop these indexes before removing property or use FORCE parameter."); + } + } + + // REMOVE THE PROPERTY + sourceClass.dropProperty(fieldName); + + return null; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + private void dropRelatedIndexes(final List> indexes) { + final ODatabaseDocument database = getDatabase(); + for (final OIndex index : indexes) { + database.command(new OCommandSQL("DROP INDEX " + index.getName())).execute(); + } + } + + private List> relatedIndexes(final String fieldName) { + final List> result = new ArrayList>(); + + final ODatabaseDocument database = getDatabase(); + for (final OIndex oIndex : database.getMetadata().getIndexManager().getClassIndexes(className)) { + if (OCollections.indexOf(oIndex.getDefinition().getFields(), fieldName, new OCaseInsentiveComparator()) > -1) { + result.add(oIndex); + } + } + + return result; + } + + @Override + public String getSyntax() { + return "DROP PROPERTY . [ IF EXISTS ] [FORCE]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropSequence.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropSequence.java new file mode 100644 index 00000000000..9584f51b640 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropSequence.java @@ -0,0 +1,63 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Map; + +/** + * @author Matan Shukry (matanshukry@gmail.com) + * @since 2/28/2015 + */ +public class OCommandExecutorSQLDropSequence extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_SEQUENCE = "SEQUENCE"; + + private String sequenceName; + + @Override public OCommandExecutorSQLDropSequence parse(OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + final ODatabaseDocumentInternal database = getDatabase(); + final StringBuilder word = new StringBuilder(); + + parserRequiredKeyword("DROP"); + parserRequiredKeyword("SEQUENCE"); + this.sequenceName = parserRequiredWord(false, "Expected "); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + @Override public Object execute(Map iArgs) { + if (this.sequenceName == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + final ODatabaseDocument database = getDatabase(); + database.getMetadata().getSequenceLibrary().dropSequence(this.sequenceName); + return true; + } + + @Override public String getSyntax() { + return "DROP SEQUENCE "; + } + + @Override public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropUser.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropUser.java new file mode 100644 index 00000000000..fa4b26a2d92 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLDropUser.java @@ -0,0 +1,69 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Map; + +/** + * Drops a use. + * + * @author Matan Shukry (matanshukry@gmail.com) + * @since 4/22/2015 + */ +public class OCommandExecutorSQLDropUser extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_DROP = "DROP"; + public static final String KEYWORD_USER = "USER"; + + private static final String SYNTAX = "DROP USER "; + private static final String USER_CLASS = "OUser"; + private static final String USER_FIELD_NAME = "name"; + + private String userName; + + @Override + public OCommandExecutorSQLDropUser parse(OCommandRequest iRequest) { + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(KEYWORD_DROP); + parserRequiredKeyword(KEYWORD_USER); + this.userName = parserRequiredWord(false, "Expected "); + + return this; + } + + @Override + public Object execute(Map iArgs) { + if (this.userName == null) { + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + } + + // Build following command: + // DELETE FROM OUser WHERE name='' + + // + StringBuilder sb = new StringBuilder(); + sb.append("DELETE FROM "); + sb.append(USER_CLASS); + sb.append(" WHERE "); + sb.append(USER_FIELD_NAME); + sb.append("='"); + sb.append(this.userName); + sb.append("'"); + + // + return getDatabase().command(new OCommandSQL(sb.toString())).execute(); + } + + @Override + public String getSyntax() { + return SYNTAX; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLEarlyResultsetAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLEarlyResultsetAbstract.java new file mode 100644 index 00000000000..ba05d7f28f4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLEarlyResultsetAbstract.java @@ -0,0 +1,50 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Abstract class that early executes the command and provide the iterator interface on top of the resultset. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public abstract class OCommandExecutorSQLEarlyResultsetAbstract extends OCommandExecutorSQLResultsetAbstract { + private Iterator iterator; + + public Iterator iterator() { + return iterator(null); + } + + @Override + public Iterator iterator(Map iArgs) { + if (iterator == null) { + if (tempResult == null) + tempResult = (List) execute(iArgs); + iterator = tempResult.iterator(); + } + return iterator; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLExplain.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLExplain.java new file mode 100644 index 00000000000..a189aa89b59 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLExplain.java @@ -0,0 +1,99 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Map; + +/** + * Explains the execution of a command returning profiling information. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLExplain extends OCommandExecutorSQLDelegate { + public static final String KEYWORD_EXPLAIN = "EXPLAIN"; + + @SuppressWarnings("unchecked") + @Override + public OCommandExecutorSQLExplain parse(OCommandRequest iCommand) { + final OCommandRequestText textRequest = (OCommandRequestText) iCommand; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iCommand); + textRequest.setText(queryText); + + final String cmd = ((OCommandRequestText) iCommand).getText(); + super.parse(new OCommandSQL(cmd.substring(KEYWORD_EXPLAIN.length()))); + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + @Override + public Object execute(Map iArgs) { + delegate.getContext().setRecordingMetrics(true); + + final long startTime = System.nanoTime(); + + final Object result = super.execute(iArgs); + final ODocument report = new ODocument(delegate.getContext().getVariables()); + + report.field("elapsed", (System.nanoTime() - startTime) / 1000000f); + + if (result instanceof Collection) { + report.field("resultType", "collection"); + report.field("resultSize", ((Collection) result).size()); + } else if (result instanceof ODocument) { + report.field("resultType", "document"); + report.field("resultSize", 1); + } else if (result instanceof Number) { + report.field("resultType", "number"); + } + + return report; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.READ; + } + + @Override + public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.REPLICATE; + } + + @Override + public DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() { + return DISTRIBUTED_RESULT_MGMT.MERGE; + } + + @Override + public boolean isCacheable() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFactory.java new file mode 100644 index 00000000000..e16c2f7387c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Set; + +/** + * Factory to register new OCommandExecutorSQL. + * + * @author Johann Sorel (Geomatys) + */ +public interface OCommandExecutorSQLFactory { + + /** + * @return Set of supported command names of this factory + */ + Set getCommandNames(); + + /** + * Create command for the given name. returned command may be a new instance each time or a constant. + * + * @param name + * @return OCommandExecutorSQLAbstract : created command + * @throws OCommandExecutionException + * : when command creation fail + */ + OCommandExecutor createCommand(String name) throws OCommandExecutionException; +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFindReferences.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFindReferences.java new file mode 100755 index 00000000000..4c8ffa82efa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLFindReferences.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * FIND REFERENCES command: Finds references to records in all or part of database + * + * @author Luca Molino + * + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLFindReferences extends OCommandExecutorSQLEarlyResultsetAbstract { + public static final String KEYWORD_FIND = "FIND"; + public static final String KEYWORD_REFERENCES = "REFERENCES"; + + private Set recordIds = new HashSet(); + private String classList; + private StringBuilder subQuery; + + public OCommandExecutorSQLFindReferences parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + // System.out.println("NEW PARSER FROM: " + queryText); + queryText = preParse(queryText, iRequest); + // System.out.println("NEW PARSER TO: " + queryText); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(KEYWORD_FIND); + parserRequiredKeyword(KEYWORD_REFERENCES); + final String target = parserRequiredWord(true, "Expected ", " =><,\r\n"); + + if (target.charAt(0) == '(') { + subQuery = new StringBuilder(); + parserSetCurrentPosition(OStringSerializerHelper.getEmbedded(parserText, parserGetPreviousPosition(), -1, subQuery)); + } else { + try { + final ORecordId rid = new ORecordId(target); + if (!rid.isValid()) + throwParsingException("Record ID " + target + " is not valid"); + recordIds.add(rid); + + } catch (IllegalArgumentException iae) { + throw new OCommandSQLParsingException("Error reading record Id", parserText, parserGetPreviousPosition()); + } + } + + parserSkipWhiteSpaces(); + classList = parserOptionalWord(true); + if (classList != null) { + classList = parserTextUpperCase.substring(parserGetPreviousPosition()); + + if (!classList.startsWith("[") || !classList.endsWith("]")) { + throwParsingException("Class list must be contained in []"); + } + // GET THE CLUSTER LIST TO SEARCH, IF NULL WILL SEARCH ENTIRE DATABASE + classList = classList.substring(1, classList.length() - 1); + } + + return this; + }finally { + textRequest.setText(originalQuery); + } + } + + /** + * Execute the FIND REFERENCES. + */ + public Object execute(final Map iArgs) { + if (recordIds.isEmpty() && subQuery == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + if (subQuery != null) { + final List result = new OCommandSQL(subQuery.toString()).execute(); + for (OIdentifiable id : result) + recordIds.add(id.getIdentity()); + } + + return OFindReferenceHelper.findReferences(recordIds, classList); + } + + @Override + public String getSyntax() { + return "FIND REFERENCES > [class-list]"; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.NONE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLGrant.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLGrant.java new file mode 100644 index 00000000000..c471cb65a3b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLGrant.java @@ -0,0 +1,113 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.security.ORole; + +import java.util.Map; + +/** + * SQL GRANT command: Grant a privilege to a database role. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLGrant extends OCommandExecutorSQLPermissionAbstract { + public static final String KEYWORD_GRANT = "GRANT"; + private static final String KEYWORD_TO = "TO"; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLGrant parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + privilege = ORole.PERMISSION_NONE; + resource = null; + role = null; + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_GRANT)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_GRANT + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid privilege", parserText, oldPos); + + parsePrivilege(word, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ON)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ON + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserText, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid resource", parserText, oldPos); + + resource = word.toString(); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_TO)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_TO + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserText, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid role", parserText, oldPos); + + final String roleName = word.toString(); + role = getDatabase().getMetadata().getSecurity().getRole(roleName); + if (role == null) + throw new OCommandSQLParsingException("Invalid role: " + roleName); + + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the GRANT. + */ + public Object execute(final Map iArgs) { + if (role == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + role.grant(resource, privilege); + role.save(); + + return role; + } + + public String getSyntax() { + return "GRANT ON TO "; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLHide.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLHide.java new file mode 100644 index 00000000000..af72dd458c1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLHide.java @@ -0,0 +1,72 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; + +import java.util.Map; + +/** + * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) + * @since 3/21/14 + */ +public class OCommandExecutorSQLHide extends OCommandExecutorSQLAbstract { + public static final String NAME = "HIDE FROM"; + public static final String KEYWORD_HIDE = "HIDE"; + + private ORID recordIdToHide; + + public String getSyntax() { + return "HIDE FROM RID"; + } + + @Override + public OCommandExecutorSQLHide parse(OCommandRequest iRequest) { + init((OCommandRequestText) iRequest); + + parserRequiredKeyword(OCommandExecutorSQLHide.KEYWORD_HIDE); + parserRequiredKeyword(OCommandExecutorSQLHide.KEYWORD_FROM); + + final String subjectName = parserRequiredWord(false, "Syntax error", " =><,\r\n"); + recordIdToHide = new ORecordId(subjectName); + + return this; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public Object execute(Map iArgs) { + final ODatabaseDocument database = getDatabase(); + if (database.hide(recordIdToHide)) + return 1; + + return 0; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLInsert.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLInsert.java new file mode 100644 index 00000000000..a03cce7cfdf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLInsert.java @@ -0,0 +1,458 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.command.*; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.storage.OCluster; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +/** + * SQL INSERT command. + * + * @author Luca Garulli + * @author Johann Sorel (Geomatys) + */ +public class OCommandExecutorSQLInsert extends OCommandExecutorSQLSetAware + implements OCommandDistributedReplicateRequest, OCommandResultListener { + public static final String KEYWORD_INSERT = "INSERT"; + protected static final String KEYWORD_RETURN = "RETURN"; + private static final String KEYWORD_VALUES = "VALUES"; + private String className = null; + private OClass clazz = null; + private String clusterName = null; + private String indexName = null; + private List> newRecords; + private OSQLAsynchQuery subQuery = null; + private AtomicLong saved = new AtomicLong(0); + private Object returnExpression = null; + private List queryResult = null; + private boolean unsafe = false; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLInsert parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + // System.out.println("NEW PARSER FROM: " + queryText); + queryText = preParse(queryText, iRequest); + // System.out.println("NEW PARSER TO: " + queryText); + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + className = null; + newRecords = null; + content = null; + + if (parserTextUpperCase.endsWith(KEYWORD_UNSAFE)) { + unsafe = true; + parserText = parserText.substring(0, parserText.length() - KEYWORD_UNSAFE.length() - 1); + parserTextUpperCase = parserTextUpperCase.substring(0, parserTextUpperCase.length() - KEYWORD_UNSAFE.length() - 1); + } + + parserRequiredKeyword("INSERT"); + parserRequiredKeyword("INTO"); + + String subjectName = parserRequiredWord(true, "Invalid subject name. Expected cluster, class or index"); + if (subjectName.startsWith(OCommandExecutorSQLAbstract.CLUSTER_PREFIX)) { + // CLUSTER + clusterName = subjectName.substring(OCommandExecutorSQLAbstract.CLUSTER_PREFIX.length()); + try { + int clusterId = Integer.parseInt(clusterName); + clusterName = database.getClusterNameById(clusterId); + } catch (Exception e) { + //not an integer + } + } else if (subjectName.startsWith(OCommandExecutorSQLAbstract.INDEX_PREFIX)) + // INDEX + indexName = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_PREFIX.length()); + + else { + // CLASS + if (subjectName.startsWith(OCommandExecutorSQLAbstract.CLASS_PREFIX)) + subjectName = subjectName.substring(OCommandExecutorSQLAbstract.CLASS_PREFIX.length()); + + final OClass cls = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(subjectName); + if (cls == null) + throwParsingException("Class " + subjectName + " not found in database"); + + if (!unsafe && cls.isSubClassOf("E")) + // FOUND EDGE + throw new OCommandExecutionException( + "'INSERT' command cannot create Edges. Use 'CREATE EDGE' command instead, or apply the 'UNSAFE' keyword to force it"); + + className = cls.getName(); + clazz = database.getMetadata().getSchema().getClass(className); + if (clazz == null) + throw new OQueryParsingException("Class '" + className + "' was not found"); + } + + if (clusterName != null && className == null) { + ODatabaseDocumentInternal db = getDatabase(); + OCluster cluster = db.getStorage().getClusterByName(clusterName); + if (cluster != null) { + clazz = db.getMetadata().getSchema().getClassByClusterId(cluster.getId()); + if (clazz != null) { + className = clazz.getName(); + } + } + } + + parserSkipWhiteSpaces(); + if (parserIsEnded()) + throwSyntaxErrorException("Set of fields is missed. Example: (name, surname) or SET name = 'Bill'"); + + final String temp = parseOptionalWord(true); + if (parserGetLastWord().equalsIgnoreCase("cluster")) { + clusterName = parserRequiredWord(false); + + parserSkipWhiteSpaces(); + if (parserIsEnded()) + throwSyntaxErrorException("Set of fields is missed. Example: (name, surname) or SET name = 'Bill'"); + } else + parserGoBack(); + + newRecords = new ArrayList>(); + Boolean sourceClauseProcessed = false; + if (parserGetCurrentChar() == '(') { + parseValues(); + parserNextWord(true, " \r\n"); + sourceClauseProcessed = true; + } else { + parserNextWord(true, " ,\r\n"); + + if (parserGetLastWord().equals(KEYWORD_CONTENT)) { + newRecords = null; + parseContent(); + sourceClauseProcessed = true; + } else if (parserGetLastWord().equals(KEYWORD_SET)) { + final List> fields = new ArrayList>(); + parseSetFields(clazz, fields); + + newRecords.add(OPair.convertToMap(fields)); + + sourceClauseProcessed = true; + } + } + if (sourceClauseProcessed) + parserNextWord(true, " \r\n"); + // it has to be processed before KEYWORD_FROM in order to not be taken as part of SELECT + if (parserGetLastWord().equals(KEYWORD_RETURN)) { + parseReturn(!sourceClauseProcessed); + parserNextWord(true, " \r\n"); + } + + if (!sourceClauseProcessed) { + if (parserGetLastWord().equals(KEYWORD_FROM)) { + newRecords = null; + subQuery = new OSQLAsynchQuery(parserText.substring(parserGetCurrentPosition()), this); + } + } + + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the INSERT and return the ODocument object created. + */ + public Object execute(final Map iArgs) { + if (newRecords == null && content == null && subQuery == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final OCommandParameters commandParameters = new OCommandParameters(iArgs); + if (indexName != null) { + if (newRecords == null) + throw new OCommandExecutionException("No key/value found"); + + final OIndex index = getDatabase().getMetadata().getIndexManager().getIndex(indexName); + if (index == null) + throw new OCommandExecutionException("Target index '" + indexName + "' not found"); + + // BIND VALUES + Map result = new HashMap(); + + for (Map candidate : newRecords) { + Object indexKey = getIndexKeyValue(commandParameters, candidate); + OIdentifiable indexValue = getIndexValue(commandParameters, candidate); + index.put(indexKey, indexValue); + result.put(KEYWORD_KEY, indexKey); + result.put(KEYWORD_RID, indexValue); + } + + // RETURN LAST ENTRY + return prepareReturnItem(new ODocument(result)); + } else { + // CREATE NEW DOCUMENTS + final List docs = new ArrayList(); + if (newRecords != null) { + for (Map candidate : newRecords) { + final ODocument doc = className != null ? new ODocument(className) : new ODocument(); + OSQLHelper.bindParameters(doc, candidate, commandParameters, context); + + saveRecord(doc); + docs.add(doc); + } + + if (docs.size() == 1) + return prepareReturnItem(docs.get(0)); + else + return prepareReturnResult(docs); + } else if (content != null) { + final ODocument doc = className != null ? new ODocument(className) : new ODocument(); + doc.merge(content, true, false); + saveRecord(doc); + return prepareReturnItem(doc); + } else if (subQuery != null) { + OBasicCommandContext subCtx = new OBasicCommandContext(); + subCtx.setParent(this.context); + subQuery.setContext(subCtx); + subQuery.execute(); + if (queryResult != null) + return prepareReturnResult(queryResult); + + return saved.longValue(); + } + } + return null; + } + + @Override + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.LOCAL; + } + + @Override + public Set getInvolvedClusters() { + if (className != null) { + final OClass clazz = ((OMetadataInternal) getDatabase().getMetadata()).getImmutableSchemaSnapshot().getClass(className); + return Collections.singleton(getDatabase().getClusterNameById(clazz.getClusterSelection().getCluster(clazz, null))); + } else if (clusterName != null) + return getInvolvedClustersOfClusters(Collections.singleton(clusterName)); + + return Collections.EMPTY_SET; + } + + @Override + public String getSyntax() { + return "INSERT INTO [class:]|cluster:|index: [([,]*) VALUES ([,]*)[,]*]|[SET = |[,]*]|CONTENT {} [RETURN ] [FROM select-query]"; + } + + @Override + public boolean result(final Object iRecord) { + final ORecord rec = ((OIdentifiable) iRecord).getRecord().copy(); + + // RESET THE IDENTITY TO AVOID UPDATE + rec.getIdentity().reset(); + + if (rec instanceof ODocument && className != null) { + ((ODocument) rec).setClassName(className); + ((ODocument) rec).setTrackingChanges(true); + } + rec.setDirty(); + synchronized (this) { + saveRecord(rec); + if (queryResult != null) + queryResult.add(((ODocument) rec)); + } + + return true; + } + + @Override + public void end() { + + } + + protected Object prepareReturnResult(List res) { + if (returnExpression == null) + return res;// No transformation + final ArrayList ret = new ArrayList(); + for (ODocument resItem : (List) res) + ret.add(prepareReturnItem(resItem)); + return ret; + } + + protected Object prepareReturnItem(ODocument item) { + if (returnExpression == null) + return item;// No transformation + + this.getContext().setVariable("current", item); + final Object res = OSQLHelper.getValue(returnExpression, item, this.getContext()); + if (res instanceof OIdentifiable) + return res; + else {// wrapping doc + final ODocument wrappingDoc = new ODocument("result", res); + wrappingDoc.field("rid", item.getIdentity());// passing record id.In many cases usable on client side + wrappingDoc.field("version", item.getVersion());// passing record version + return wrappingDoc; + } + } + + protected void saveRecord(final ORecord rec) { + if (clusterName != null) + rec.save(clusterName); + else + rec.save(); + saved.incrementAndGet(); + } + + protected void parseValues() { + final int beginFields = parserGetCurrentPosition(); + + final int endFields = parserText.indexOf(')', beginFields + 1); + if (endFields == -1) + throwSyntaxErrorException("Missed closed brace"); + + final ArrayList fieldNamesQuoted = new ArrayList(); + parserSetCurrentPosition(OStringSerializerHelper.getParameters(parserText, beginFields, endFields, fieldNamesQuoted)); + final ArrayList fieldNames = new ArrayList(); + for (String fieldName : fieldNamesQuoted) { + fieldNames.add(decodeClassName(fieldName)); + } + + if (fieldNames.size() == 0) + throwSyntaxErrorException("Set of fields is empty. Example: (name, surname)"); + + // REMOVE QUOTATION MARKS IF ANY + for (int i = 0; i < fieldNames.size(); ++i) + fieldNames.set(i, OStringSerializerHelper.removeQuotationMarks(fieldNames.get(i))); + + parserRequiredKeyword(KEYWORD_VALUES); + parserSkipWhiteSpaces(); + if (parserIsEnded() || parserText.charAt(parserGetCurrentPosition()) != '(') { + throwParsingException("Set of values is missed. Example: ('Bill', 'Stuart', 300)"); + } + + int blockStart = parserGetCurrentPosition(); + int blockEnd = parserGetCurrentPosition(); + + final List records = OStringSerializerHelper + .smartSplit(parserText, new char[] { ',' }, blockStart, -1, true, true, false, false); + for (String record : records) { + + final List values = new ArrayList(); + blockEnd += OStringSerializerHelper.getParameters(record, 0, -1, values); + + if (blockEnd == -1) + throw new OCommandSQLParsingException("Missed closed brace. Use " + getSyntax(), parserText, blockStart); + + if (values.isEmpty()) + throw new OCommandSQLParsingException("Set of values is empty. Example: ('Bill', 'Stuart', 300). Use " + getSyntax(), + parserText, blockStart); + + if (values.size() != fieldNames.size()) + throw new OCommandSQLParsingException("Fields not match with values", parserText, blockStart); + + // TRANSFORM FIELD VALUES + final Map fields = new LinkedHashMap(); + for (int i = 0; i < values.size(); ++i) + fields.put(fieldNames.get(i), OSQLHelper.parseValue(this, values.get(i).trim(), context)); + + newRecords.add(fields); + blockStart = blockEnd; + } + + } + + /** + * Parses the returning keyword if found. + */ + protected void parseReturn(Boolean subQueryExpected) throws OCommandSQLParsingException { + parserNextWord(false, " "); + String returning = parserGetLastWord().trim(); + if (returning.startsWith("$") || returning.startsWith("@")) { + if (subQueryExpected) + queryResult = new ArrayList(); + returnExpression = (returning.length() > 0) ? OSQLHelper.parseValue(this, returning, this.getContext()) : null; + } else + throwSyntaxErrorException("record attribute (@attributes) or functions with $current variable expected"); + + } + + private Object getIndexKeyValue(OCommandParameters commandParameters, Map candidate) { + final Object parsedKey = candidate.get(KEYWORD_KEY); + if (parsedKey instanceof OSQLFilterItemField) { + final OSQLFilterItemField f = (OSQLFilterItemField) parsedKey; + if (f.getRoot().equals("?")) + // POSITIONAL PARAMETER + return commandParameters.getNext(); + else if (f.getRoot().startsWith(":")) { + // NAMED PARAMETER + return commandParameters.getByName(f.getRoot().substring(1)); + } else { + return f.getValue(new ODocument(), null, context); + } + } else if (parsedKey instanceof OSQLFunctionRuntime) { + OSQLFunctionRuntime f = (OSQLFunctionRuntime) parsedKey; + return f.execute(null, null, null, context); + } + return parsedKey; + } + + private OIdentifiable getIndexValue(OCommandParameters commandParameters, Map candidate) { + final Object parsedRid = candidate.get(KEYWORD_RID); + if (parsedRid instanceof OSQLFilterItemField) { + final OSQLFilterItemField f = (OSQLFilterItemField) parsedRid; + if (f.getRoot().equals("?")) + // POSITIONAL PARAMETER + return (OIdentifiable) commandParameters.getNext(); + else if (f.getRoot().startsWith(":")) + // NAMED PARAMETER + return (OIdentifiable) commandParameters.getByName(f.getRoot().substring(1)); + } + return (OIdentifiable) parsedRid; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } + + @Override + public Object getResult() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveSelect.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveSelect.java new file mode 100644 index 00000000000..1439033b757 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveSelect.java @@ -0,0 +1,246 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.command.OCommandResultListener; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordOperation; +import com.orientechnologies.orient.core.exception.OSecurityException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.security.ORestrictedAccessHook; +import com.orientechnologies.orient.core.metadata.security.ORestrictedOperation; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.query.live.OLiveQueryHook; +import com.orientechnologies.orient.core.query.live.OLiveQueryListener; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OLiveResultListener; +import com.orientechnologies.orient.core.sql.query.OResultSet; + +import java.util.Map; +import java.util.Random; + +/** + * @author Luigi Dell'Aquila + */ +public class OCommandExecutorSQLLiveSelect extends OCommandExecutorSQLSelect implements OLiveQueryListener { + public static final String KEYWORD_LIVE_SELECT = "LIVE SELECT"; + private ODatabaseDocument execDb; + private int token; + private static final Random random = new Random(); + + public OCommandExecutorSQLLiveSelect() { + + } + + public Object execute(final Map iArgs) { + try { + final ODatabaseDocumentInternal db = getDatabase(); + execInSeparateDatabase(new OCallable() { + @Override public Object call(Object iArgument) { + return execDb = ((ODatabaseDocumentTx) db).copy(); + } + }); + + synchronized (random) { + token = random.nextInt();// TODO do something better ;-)! + } + subscribeToLiveQuery(token, db); + bindDefaultContextVariables(); + + if (iArgs != null) + // BIND ARGUMENTS INTO CONTEXT TO ACCESS FROM ANY POINT (EVEN FUNCTIONS) + { + for (Map.Entry arg : iArgs.entrySet()) { + context.setVariable(arg.getKey().toString(), arg.getValue()); + } + } + + if (timeoutMs > 0) { + getContext().beginExecution(timeoutMs, timeoutStrategy); + } + + ODocument result = new ODocument(); + result.field("token", token);// TODO change this name...? + + ((OResultSet) getResult()).add(result); + return getResult(); + } finally { + if (request != null && request.getResultListener() != null) { + request.getResultListener().end(); + } + } + } + + private void subscribeToLiveQuery(Integer token, ODatabaseInternal db) { + OLiveQueryHook.subscribe(token, this, db); + } + + public void onLiveResult(final ORecordOperation iOp) { + + ODatabaseDocumentInternal oldThreadLocal = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + execDb.activateOnCurrentThread(); + + try { + final OIdentifiable value = iOp.getRecord(); + + if (!matchesTarget(value)) { + return; + } + if (!matchesFilters(value)) { + return; + } + if (!checkSecurity(value)) { + return; + } + } finally { + if (oldThreadLocal == null) { + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } else { + ODatabaseRecordThreadLocal.INSTANCE.set(oldThreadLocal); + } + } + final OCommandResultListener listener = request.getResultListener(); + if (listener instanceof OLiveResultListener) { + execInSeparateDatabase(new OCallable() { + @Override public Object call(Object iArgument) { + execDb.activateOnCurrentThread(); + ((OLiveResultListener) listener).onLiveResult(token, iOp); + return null; + } + }); + } + } + + protected void execInSeparateDatabase(final OCallable iCallback) { + final ODatabaseDocumentInternal prevDb = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + try { + iCallback.call(null); + } finally { + if (prevDb != null) { + ODatabaseRecordThreadLocal.INSTANCE.set(prevDb); + } else { + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } + } + } + + private boolean checkSecurity(OIdentifiable value) { + try { + // TODO check this! + execDb.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, ((ODocument) value.getRecord()).getClassName()); + } catch (OSecurityException e) { + return false; + } + return ORestrictedAccessHook.isAllowed(execDb, (ODocument) value.getRecord(), ORestrictedOperation.ALLOW_READ, false); + } + + private boolean matchesFilters(OIdentifiable value) { + if (this.compiledFilter == null || this.compiledFilter.getRootCondition() == null) { + return true; + } + if (!(value instanceof ODocument)) { + value = value.getRecord(); + } + return !(Boolean.FALSE.equals(compiledFilter.evaluate((ODocument) value, (ODocument) value, getContext()))); + } + + private boolean matchesTarget(OIdentifiable value) { + if (!(value instanceof ODocument)) { + return false; + } + final String className = ((ODocument) value).getClassName(); + if (className == null) { + return false; + } + final OClass docClass = execDb.getMetadata().getSchema().getClass(className); + if (docClass == null) { + return false; + } + + if (this.parsedTarget.getTargetClasses() != null) { + for (String clazz : parsedTarget.getTargetClasses().keySet()) { + if (docClass.isSubClassOf(clazz)) { + return true; + } + } + } + if (this.parsedTarget.getTargetRecords() != null) { + for (OIdentifiable r : parsedTarget.getTargetRecords()) { + if (r.getIdentity().equals(value.getIdentity())) { + return true; + } + } + } + if (this.parsedTarget.getTargetClusters() != null) { + final String clusterName = execDb.getClusterNameById(value.getIdentity().getClusterId()); + if (clusterName != null) { + for (String cluster : parsedTarget.getTargetClusters().keySet()) { + if (clusterName.equalsIgnoreCase(cluster)) {//make it case insensitive in 3.0? + return true; + } + } + } + } + return false; + } + + public void onLiveResultEnd() { + if (request.getResultListener() instanceof OLiveResultListener) { + ((OLiveResultListener) request.getResultListener()).onUnsubscribe(token); + } + + if (execDb != null) { + ODatabaseDocumentInternal oldThreadDB = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + execDb.activateOnCurrentThread(); + execDb.close(); + if (oldThreadDB == null) { + ODatabaseRecordThreadLocal.INSTANCE.remove(); + } else { + ODatabaseRecordThreadLocal.INSTANCE.set(oldThreadDB); + } + } + } + + @Override public OCommandExecutorSQLSelect parse(final OCommandRequest iRequest) { + final OCommandRequestText requestText = (OCommandRequestText) iRequest; + final String originalText = requestText.getText(); + final String remainingText = requestText.getText().trim().substring(5).trim(); + requestText.setText(remainingText); + try { + return super.parse(iRequest); + } finally { + requestText.setText(originalText); + } + } + + @Override public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.NONE; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveUnsubscribe.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveUnsubscribe.java new file mode 100644 index 00000000000..540a4b74901 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLLiveUnsubscribe.java @@ -0,0 +1,105 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.query.live.OLiveQueryHook; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Locale; +import java.util.Map; + +/** + * @author Luigi Dell'Aquila + */ +public class OCommandExecutorSQLLiveUnsubscribe extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_LIVE_UNSUBSCRIBE = "LIVE UNSUBSCRIBE"; + + protected String unsubscribeToken; + + public OCommandExecutorSQLLiveUnsubscribe() { + } + + private Object executeUnsubscribe() { + try { + + OLiveQueryHook.unsubscribe(Integer.parseInt(unsubscribeToken), getDatabase()); + ODocument result = new ODocument(); + result.field("unsubscribed", unsubscribeToken); + result.field("unsubscribe", true); + result.field("token", unsubscribeToken); + + return result; + } catch (Exception e) { + OLogManager.instance().warn(this, + "error unsubscribing token " + unsubscribeToken + ": " + e.getClass().getName() + " - " + e.getMessage()); + ODocument result = new ODocument(); + result.field("error-unsubscribe", unsubscribeToken); + result.field("error-description", e.getMessage()); + result.field("error-type", e.getClass().getName()); + + return result; + } + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + public Object execute(final Map iArgs) { + if (this.unsubscribeToken != null) { + return executeUnsubscribe(); + } + ODocument result = new ODocument(); + result.field("error-unsubscribe", "no token"); + return result; + } + + @Override + public OCommandExecutorSQLLiveUnsubscribe parse(OCommandRequest iRequest) { + OCommandRequestText requestText = (OCommandRequestText) iRequest; + String originalText = requestText.getText(); + String remainingText = requestText.getText().trim().substring(5).trim(); + requestText.setText(remainingText); + try { + if (remainingText.toLowerCase(Locale.ENGLISH).startsWith("unsubscribe")) { + remainingText = remainingText.substring("unsubscribe".length()).trim(); + if (remainingText.contains(" ")) { + throw new OQueryParsingException("invalid unsubscribe token for live query: " + remainingText); + } + this.unsubscribeToken = remainingText; + } + } finally { + requestText.setText(originalText); + } + return this; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.NONE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLOptimizeDatabase.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLOptimizeDatabase.java new file mode 100644 index 00000000000..02f4e716082 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLOptimizeDatabase.java @@ -0,0 +1,213 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.intent.OIntentMassiveInsert; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Iterator; +import java.util.Map; + +/** + * SQL ALTER DATABASE command: Changes an attribute of the current database. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLOptimizeDatabase extends OCommandExecutorSQLAbstract + implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_OPTIMIZE = "OPTIMIZE"; + public static final String KEYWORD_DATABASE = "DATABASE"; + public static final String KEYWORD_EDGE = "-LWEDGES"; + public static final String KEYWORD_NOVERBOSE = "-NOVERBOSE"; + + private boolean optimizeEdges = false; + private boolean verbose = true; + private int batch = 1000; + + public OCommandExecutorSQLOptimizeDatabase parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_OPTIMIZE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_OPTIMIZE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_DATABASE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_DATABASE + " not found. Use " + getSyntax(), parserText, oldPos); + + while (!parserIsEnded() && word.length() > 0) { + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (word.toString().equals(KEYWORD_EDGE)) + optimizeEdges = true; + else if (word.toString().equals(KEYWORD_NOVERBOSE)) + verbose = false; + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the ALTER DATABASE. + */ + public Object execute(final Map iArgs) { + final StringBuilder result = new StringBuilder(); + + if (optimizeEdges) + result.append(optimizeEdges()); + + return result.toString(); + } + + private String optimizeEdges() { + final ODatabaseDocumentInternal db = getDatabase(); + + db.declareIntent(new OIntentMassiveInsert()); + try { + long transformed = 0; + if (db.getTransaction().isActive()) + db.commit(); + + db.begin(); + + try { + + final long totalEdges = db.countClass("E"); + long browsedEdges = 0; + long lastLapBrowsed = 0; + long lastLapTime = System.currentTimeMillis(); + + for (ODocument doc : db.browseClass("E")) { + if (Thread.currentThread().isInterrupted()) + break; + + browsedEdges++; + + if (doc != null) { + if (doc.fields() == 2) { + final ORID edgeIdentity = doc.getIdentity(); + + final ODocument outV = doc.field("out"); + final ODocument inV = doc.field("in"); + + // OUTGOING + final Object outField = outV.field("out_" + doc.getClassName()); + if (outField instanceof ORidBag) { + final Iterator it = ((ORidBag) outField).iterator(); + while (it.hasNext()) { + OIdentifiable v = it.next(); + if (edgeIdentity.equals(v)) { + // REPLACE EDGE RID WITH IN-VERTEX RID + it.remove(); + ((ORidBag) outField).add(inV.getIdentity()); + break; + } + } + } + + outV.save(); + + // INCOMING + final Object inField = inV.field("in_" + doc.getClassName()); + if (outField instanceof ORidBag) { + final Iterator it = ((ORidBag) inField).iterator(); + while (it.hasNext()) { + OIdentifiable v = it.next(); + if (edgeIdentity.equals(v)) { + // REPLACE EDGE RID WITH IN-VERTEX RID + it.remove(); + ((ORidBag) inField).add(outV.getIdentity()); + break; + } + } + } + + inV.save(); + + doc.delete(); + + if (++transformed % batch == 0) { + db.commit(); + db.begin(); + } + + final long now = System.currentTimeMillis(); + + if (verbose && (now - lastLapTime > 2000)) { + final long elapsed = now - lastLapTime; + + OLogManager.instance().info(this, "Browsed %,d of %,d edges, transformed %,d so far (%,d edges/sec)", + browsedEdges, totalEdges, + transformed, + (((browsedEdges - lastLapBrowsed) * 1000 / elapsed))); + + lastLapTime = System.currentTimeMillis(); + lastLapBrowsed = browsedEdges; + } + } + } + } + + // LAST COMMIT + db.commit(); + + } finally { + if (db.getTransaction().isActive()) + db.rollback(); + } + return "Transformed " + transformed + " regular edges in lightweight edges"; + + } finally { + db.declareIntent(null); + } + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } + + public String getSyntax() { + return "OPTIMIZE DATABASE [-lwedges]"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLPermissionAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLPermissionAbstract.java new file mode 100644 index 00000000000..6502591955f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLPermissionAbstract.java @@ -0,0 +1,63 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.metadata.security.ORole; + +/** + * SQL GRANT command: Grant a privilege to a database role. + * + * @author Luca Garulli + * + */ +public abstract class OCommandExecutorSQLPermissionAbstract extends OCommandExecutorSQLAbstract { + protected static final String KEYWORD_ON = "ON"; + protected int privilege; + protected String resource; + protected ORole role; + + protected void parsePrivilege(final StringBuilder word, final int oldPos) { + final String privilegeName = word.toString(); + + if ("CREATE".equals(privilegeName)) + privilege = ORole.PERMISSION_CREATE; + else if ("READ".equals(privilegeName)) + privilege = ORole.PERMISSION_READ; + else if ("UPDATE".equals(privilegeName)) + privilege = ORole.PERMISSION_UPDATE; + else if ("DELETE".equals(privilegeName)) + privilege = ORole.PERMISSION_DELETE; + else if ("EXECUTE".equals(privilegeName)) + privilege = ORole.PERMISSION_EXECUTE; + else if ("ALL".equals(privilegeName)) + privilege = ORole.PERMISSION_ALL; + else if ("NONE".equals(privilegeName)) + privilege = ORole.PERMISSION_NONE; + else + throw new OCommandSQLParsingException("Unrecognized privilege '" + privilegeName + "'", parserText, oldPos); + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRebuildIndex.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRebuildIndex.java new file mode 100644 index 00000000000..27c0663a81b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRebuildIndex.java @@ -0,0 +1,118 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.index.OIndex; + +import java.util.Map; + +/** + * SQL REMOVE INDEX command: Remove an index + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLRebuildIndex extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_REBUILD = "REBUILD"; + public static final String KEYWORD_INDEX = "INDEX"; + + private String name; + + public OCommandExecutorSQLRebuildIndex parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + init((OCommandRequestText) iRequest); + + final StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_REBUILD)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_REBUILD + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_INDEX)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_INDEX + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false); + if (pos == -1) + throw new OCommandSQLParsingException("Expected index name", parserText, oldPos); + + name = word.toString(); + + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the REMOVE INDEX. + */ + public Object execute(final Map iArgs) { + if (name == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocument database = getDatabase(); + if (name.equals("*")) { + long totalIndexed = 0; + for (OIndex idx : database.getMetadata().getIndexManager().getIndexes()) { + if (idx.isAutomatic()) + totalIndexed += idx.rebuild(); + } + + return totalIndexed; + + } else { + final OIndex idx = database.getMetadata().getIndexManager().getIndex(name); + if (idx == null) + throw new OCommandExecutionException("Index '" + name + "' not found"); + + if (!idx.isAutomatic()) + throw new OCommandExecutionException("Cannot rebuild index '" + name + + "' because it's manual and there aren't indications of what to index"); + + return idx.rebuild(); + } + } + + @Override + public String getSyntax() { + return "REBUILD INDEX "; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.ALL; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetAbstract.java new file mode 100755 index 00000000000..05b7fbaf1d0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetAbstract.java @@ -0,0 +1,735 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClassDescendentOrder; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClusters; +import com.orientechnologies.orient.core.metadata.OMetadataDefault; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilter; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLTarget; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorNotEquals; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorNotEquals2; +import com.orientechnologies.orient.core.sql.query.OResultSet; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Executes a TRAVERSE crossing records. Returns a List containing all the traversed records that match the WHERE + * condition. + *

          + * SYNTAX: TRAVERSE * FROM WHERE + *

          + *

          + * In the command context you've access to the variable $depth containing the depth level from the root node. This is useful to + * limit the traverse up to a level. For example to consider from the first depth level (0 is root node) to the third use: + * TRAVERSE children FROM #5:23 WHERE $depth BETWEEN 1 AND 3. To filter traversed records use it combined with a SELECT + * statement: + *

          + *

          + * SELECT FROM (TRAVERSE children FROM #5:23 WHERE $depth BETWEEN 1 AND 3) WHERE city.name = 'Rome' + *

          + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public abstract class OCommandExecutorSQLResultsetAbstract extends OCommandExecutorSQLAbstract implements + OCommandDistributedReplicateRequest, Iterable, OIterableRecordSource { + protected static final String KEYWORD_FROM_2FIND = " " + KEYWORD_FROM + " "; + protected static final String KEYWORD_LET_2FIND = " " + KEYWORD_LET + " "; + + protected OSQLAsynchQuery request; + protected OSQLTarget parsedTarget; + protected OSQLFilter compiledFilter; + protected Map let = null; + protected Iterator target; + protected Iterable tempResult; + protected int resultCount; + protected AtomicInteger serialTempRID = new AtomicInteger(0); + protected int skip = 0; + protected boolean lazyIteration = true; + + private static final class IndexValuesIterator implements Iterator { + private OIndexCursor indexCursor; + private OIdentifiable nextValue; + private boolean noItems; + + private IndexValuesIterator(String indexName, boolean ascOrder) { + if (ascOrder) + indexCursor = getDatabase().getMetadata().getIndexManager().getIndex(indexName).cursor(); + else + indexCursor = getDatabase().getMetadata().getIndexManager().getIndex(indexName).descCursor(); + } + + @Override + public boolean hasNext() { + if (noItems) + return false; + + if (nextValue == null) { + final Map.Entry entry = indexCursor.nextEntry(); + if (entry == null) { + noItems = true; + return false; + } + + nextValue = entry.getValue(); + } + + return true; + } + + @Override + public OIdentifiable next() { + if (!hasNext()) + throw new NoSuchElementException(); + + final OIdentifiable value = nextValue; + nextValue = null; + + return value; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + /** + * Compile the filter conditions only the first time. + */ + public OCommandExecutorSQLResultsetAbstract parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + init(textRequest); + + if (iRequest instanceof OSQLSynchQuery) { + request = (OSQLSynchQuery) iRequest; + } else if (iRequest instanceof OSQLAsynchQuery) + request = (OSQLAsynchQuery) iRequest; + else { + // BUILD A QUERY OBJECT FROM THE COMMAND REQUEST + request = new OSQLSynchQuery(textRequest.getText()); + if (textRequest.getResultListener() != null) + request.setResultListener(textRequest.getResultListener()); + } + return this; + } + + @Override + public boolean isIdempotent() { + return true; + } + + public boolean isLazyIteration() { + return lazyIteration; + } + + public void setLazyIteration(final boolean lazyIteration) { + this.lazyIteration = lazyIteration; + } + + @Override + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.REPLICATE; + } + + @Override + public DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() { + return DISTRIBUTED_RESULT_MGMT.MERGE; + } + + /** + * Assign the right TARGET if found. + * + * @param iArgs + * Parameters to bind + * @return true if the target has been recognized, otherwise false + */ + protected boolean assignTarget(final Map iArgs) { + parameters = iArgs; + if (parsedTarget == null) + return true; + + if (iArgs != null && iArgs.size() > 0 && compiledFilter != null) + compiledFilter.bindParameters(iArgs); + + if (target == null) { + if (parsedTarget.getTargetClasses() != null) + searchInClasses(); + else if (parsedTarget.getTargetIndexValues() != null) { + target = new IndexValuesIterator(parsedTarget.getTargetIndexValues(), parsedTarget.isTargetIndexValuesAsc()); + } else if (parsedTarget.getTargetClusters() != null) + searchInClusters(); + else if (parsedTarget.getTargetRecords() != null) { + if (!lazyIteration && parsedTarget.getTargetQuery() != null) { + // EXECUTE THE QUERY TO ALLOW DISTRIB EXECUTION + target = ((Iterable) getDatabase().command(new OCommandSQL(parsedTarget.getTargetQuery())) + .execute(iArgs)).iterator(); + } else if (parsedTarget.getTargetRecords() instanceof OIterableRecordSource) { + target = ((OIterableRecordSource) parsedTarget.getTargetRecords()).iterator(iArgs); + } else { + target = parsedTarget.getTargetRecords().iterator(); + } + } else if (parsedTarget.getTargetVariable() != null) { + final Object var = getContext().getVariable(parsedTarget.getTargetVariable()); + if (var == null) { + target = Collections.EMPTY_LIST.iterator(); + return true; + } else if (var instanceof OIdentifiable) { + final ArrayList list = new ArrayList(); + list.add((OIdentifiable) var); + target = list.iterator(); + } else if (var instanceof Iterable) + target = ((Iterable) var).iterator(); + } else + return false; + } + + return true; + } + + protected Object getResultInstance() { + if (request instanceof OSQLSynchQuery) + return ((OSQLSynchQuery) request).getResult(); + + return request.getResultListener().getResult(); + } + + protected Object getResult() { + try { + if (tempResult != null) { + int fetched = 0; + + for (Object d : tempResult) + if (d != null) { + if (!(d instanceof OIdentifiable)) + // NON-DOCUMENT AS RESULT, COMES FROM EXPAND? CREATE A DOCUMENT AT THE FLY + d = new ODocument().field("value", d); + else + d = ((OIdentifiable) d).getRecord(); + + if (limit > -1 && fetched >= limit) + break; + + if (!pushResult(d)) + break; + + ++fetched; + } + } + + return getResultInstance(); + } finally { + request.getResultListener().end(); + } + } + + protected boolean pushResult(final Object rec) { + if (rec instanceof ORecord) { + final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + db.getLocalCache().updateRecord((ORecord) rec); + } + + return request.getResultListener().result(rec); + } + + protected boolean handleResult(final OIdentifiable iRecord, final OCommandContext iContext) { + if (iRecord != null) { + resultCount++; + + OIdentifiable identifiable = iRecord instanceof ORecord ? ((ORecord) iRecord) : iRecord.getIdentity(); + + // CALL THE LISTENER NOW + if (identifiable != null && request.getResultListener() != null) { + final boolean result = pushResult(identifiable); + if (!result) + return false; + } + + if (limit > -1 && resultCount >= limit) + // BREAK THE EXECUTION + return false; + } + return true; + } + + protected void parseLet() { + let = new LinkedHashMap(); + + boolean stop = false; + while (!stop) { + // PARSE THE KEY + final String letName = parserNextWord(false); + + parserOptionalKeyword("="); + + parserNextWord(false, " =><,\r\n", true); + + // PARSE THE VALUE + String letValueAsString = parserGetLastWord(); + final Object letValue; + + // TRY TO PARSE AS FUNCTION + final Object func = OSQLHelper.getFunction(parsedTarget, letValueAsString); + if (func != null) + letValue = func; + else if (letValueAsString.startsWith("(")) { + letValue = new OSQLSynchQuery(letValueAsString.substring(1, letValueAsString.length() - 1)); + } else + letValue = letValueAsString; + + let.put(letName, letValue); + stop = parserGetLastSeparator() == ' '; + } + } + + /** + * Parses the limit keyword if found. + * + * @param w + * @return the limit found as integer, or -1 if no limit is found. -1 means no limits. + * @throws OCommandSQLParsingException + * if no valid limit has been found + */ + protected int parseLimit(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_LIMIT)) + return -1; + + final String word = parserNextWord(true); + + try { + limit = Integer.parseInt(word); + } catch (Exception e) { + throwParsingException("Invalid LIMIT value setted to '" + word + "' but it should be a valid integer. Example: LIMIT 10"); + } + + if (limit == 0) + throwParsingException("Invalid LIMIT value setted to ZERO. Use -1 to ignore the limit or use a positive number. Example: LIMIT 10"); + + return limit; + } + + /** + * Parses the skip keyword if found. + * + * @param w + * @return the skip found as integer, or -1 if no skip is found. -1 means no skip. + * @throws OCommandSQLParsingException + * if no valid skip has been found + */ + protected int parseSkip(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_SKIP) && !w.equals(KEYWORD_OFFSET)) + return -1; + + final String word = parserNextWord(true); + + try { + skip = Integer.parseInt(word); + + } catch (Exception e) { + throwParsingException("Invalid SKIP value setted to '" + word + + "' but it should be a valid positive integer. Example: SKIP 10"); + } + + if (skip < 0) + throwParsingException("Invalid SKIP value setted to the negative number '" + word + + "'. Only positive numbers are valid. Example: SKIP 10"); + + return skip; + } + + protected boolean filter(final ORecord iRecord, final OCommandContext iContext) { + if (iRecord instanceof ODocument) { + // CHECK THE TARGET CLASS + final ODocument recordSchemaAware = (ODocument) iRecord; + Map targetClasses = parsedTarget.getTargetClasses(); + // check only classes that specified in query will go to result set + if ((targetClasses != null) && (!targetClasses.isEmpty())) { + for (String targetClass : targetClasses.keySet()) { + if (!((OMetadataDefault) getDatabase().getMetadata()).getImmutableSchemaSnapshot().getClass(targetClass) + .isSuperClassOf(ODocumentInternal.getImmutableSchemaClass(recordSchemaAware))) + return false; + } + iContext.updateMetric("documentAnalyzedCompatibleClass", +1); + } + } + + return evaluateRecord(iRecord, iContext); + } + + protected boolean evaluateRecord(final ORecord iRecord, final OCommandContext iContext) { + iContext.setVariable("current", iRecord); + iContext.updateMetric("evaluated", +1); + + assignLetClauses(iRecord); + if (compiledFilter == null) + return true; + return Boolean.TRUE.equals(compiledFilter.evaluate(iRecord, null, iContext)); + } + + protected void assignLetClauses(final ORecord iRecord) { + if (let != null && !let.isEmpty()) { + // BIND CONTEXT VARIABLES + for (Map.Entry entry : let.entrySet()) { + String varName = entry.getKey(); + if (varName.startsWith("$")) + varName = varName.substring(1); + + final Object letValue = entry.getValue(); + + Object varValue; + if (letValue instanceof OSQLSynchQuery) { + final OSQLSynchQuery subQuery = (OSQLSynchQuery) letValue; + subQuery.reset(); + subQuery.resetPagination(); + subQuery.getContext().setParent(context); + subQuery.getContext().setVariable("parentQuery", this); + subQuery.getContext().setVariable("current", iRecord); + varValue = ODatabaseRecordThreadLocal.INSTANCE.get().query(subQuery); + if (varValue instanceof OResultSet) { + varValue = ((OResultSet) varValue).copy(); + } + + } else if (letValue instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) letValue; + if (f.getFunction().aggregateResults()) { + f.execute(iRecord, iRecord, null, context); + varValue = f.getFunction().getResult(); + } else + varValue = f.execute(iRecord, iRecord, null, context); + } else if (letValue instanceof String) + varValue = ODocumentHelper.getFieldValue(iRecord, ((String) letValue).trim(), context); + else + varValue = letValue; + + context.setVariable(varName, varValue); + } + } + } + + protected void searchInClasses() { + searchInClasses(true); + } + + protected void searchInClasses(final boolean iAscendentOrder) { + final String cls = parsedTarget.getTargetClasses().keySet().iterator().next(); + target = searchInClasses(getDatabase().getMetadata().getSchema().getClass(cls), true, iAscendentOrder); + } + + protected Iterator searchInClasses(final OClass iCls, final boolean iPolymorphic, + final boolean iAscendentOrder) { + + final ODatabaseDocumentInternal database = getDatabase(); + database.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, iCls.getName().toLowerCase(Locale.ENGLISH)); + + final ORID[] range = getRange(); + if (iAscendentOrder) + return new ORecordIteratorClass(database, database, iCls.getName(), iPolymorphic, isUseCache(), false).setRange(range[0], + range[1]); + else + return new ORecordIteratorClassDescendentOrder(database, database, iCls.getName(), iPolymorphic).setRange(range[0], + range[1]); + } + + protected boolean isUseCache() { + return request.isUseCache(); + } + + protected void searchInClusters() { + final ODatabaseDocumentInternal database = getDatabase(); + + final Set clusterIds = new HashSet(); + for (String clusterName : parsedTarget.getTargetClusters().keySet()) { + if (clusterName == null || clusterName.length() == 0) + throw new OCommandExecutionException("No cluster or schema class selected in query"); + + database.checkSecurity(ORule.ResourceGeneric.CLUSTER, ORole.PERMISSION_READ, clusterName.toLowerCase(Locale.ENGLISH)); + + if (Character.isDigit(clusterName.charAt(0))) { + // GET THE CLUSTER NUMBER + for (int clusterId : OStringSerializerHelper.splitIntArray(clusterName)) { + if (clusterId == -1) + throw new OCommandExecutionException("Cluster '" + clusterName + "' not found"); + + clusterIds.add(clusterId); + } + } else { + // GET THE CLUSTER NUMBER BY THE CLASS NAME + final int clusterId = database.getClusterIdByName(clusterName.toLowerCase(Locale.ENGLISH)); + if (clusterId == -1) + throw new OCommandExecutionException("Cluster '" + clusterName + "' not found"); + + clusterIds.add(clusterId); + } + } + + // CREATE CLUSTER AS ARRAY OF INT + final int[] clIds = new int[clusterIds.size()]; + int i = 0; + for (int c : clusterIds) + clIds[i++] = c; + + final ORID[] range = getRange(); + + target = new ORecordIteratorClusters(database, database, clIds).setRange(range[0], range[1]); + } + + protected void applyLimitAndSkip() { + if (tempResult != null && (limit > 0 || skip > 0)) { + final List newList = new ArrayList(); + + // APPLY LIMIT + if (tempResult instanceof List) { + final List t = (List) tempResult; + final int start = Math.min(skip, t.size()); + + int tot = t.size(); + if (limit > -1) { + tot = Math.min(limit + start, tot); + } + for (int i = start; i < tot; ++i) + newList.add(t.get(i)); + + t.clear(); + tempResult = newList; + } + } + } + + /** + * Optimizes the condition tree. + */ + protected void optimize() { + if (compiledFilter != null) + optimizeBranch(null, compiledFilter.getRootCondition()); + } + + /** + * Check function arguments and pre calculate it if possible + * + * @param function + * @return optimized function, same function if no change + */ + protected Object optimizeFunction(OSQLFunctionRuntime function) { + // boolean precalculate = true; + // for (int i = 0; i < function.configuredParameters.length; ++i) { + // if (function.configuredParameters[i] instanceof OSQLFilterItemField) { + // precalculate = false; + // } else if (function.configuredParameters[i] instanceof OSQLFunctionRuntime) { + // final Object res = optimizeFunction((OSQLFunctionRuntime) function.configuredParameters[i]); + // function.configuredParameters[i] = res; + // if (res instanceof OSQLFunctionRuntime || res instanceof OSQLFilterItemField) { + // // function might have been optimized but result is still not static + // precalculate = false; + // } + // } + // } + // + // if (precalculate) { + // // all fields are static, we can calculate it only once. + // return function.execute(null, null, null); // we can pass nulls here, they wont be used + // } else { + return function; + // } + } + + protected void optimizeBranch(final OSQLFilterCondition iParentCondition, OSQLFilterCondition iCondition) { + if (iCondition == null) + return; + + Object left = iCondition.getLeft(); + + if (left instanceof OSQLFilterCondition) { + // ANALYSE LEFT RECURSIVELY + optimizeBranch(iCondition, (OSQLFilterCondition) left); + } else if (left instanceof OSQLFunctionRuntime) { + left = optimizeFunction((OSQLFunctionRuntime) left); + iCondition.setLeft(left); + } + + Object right = iCondition.getRight(); + + if (right instanceof OSQLFilterCondition) { + // ANALYSE RIGHT RECURSIVELY + optimizeBranch(iCondition, (OSQLFilterCondition) right); + } else if (right instanceof OSQLFunctionRuntime) { + right = optimizeFunction((OSQLFunctionRuntime) right); + iCondition.setRight(right); + } + + final OQueryOperator oper = iCondition.getOperator(); + + Object result = null; + + if (left instanceof OSQLFilterItemField && right instanceof OSQLFilterItemField) { + if (((OSQLFilterItemField) left).getRoot().equals(((OSQLFilterItemField) right).getRoot())) { + if (oper instanceof OQueryOperatorEquals) + result = Boolean.TRUE; + else if ((oper instanceof OQueryOperatorNotEquals) || (oper instanceof OQueryOperatorNotEquals2)) + result = Boolean.FALSE; + } + } + + if (result != null) { + if (iParentCondition != null) + if (iCondition == iParentCondition.getLeft()) + // REPLACE LEFT + iCondition.setLeft(result); + else + // REPLACE RIGHT + iCondition.setRight(result); + else { + // REPLACE ROOT CONDITION + if (result instanceof Boolean && ((Boolean) result)) + compiledFilter.setRootCondition(null); + } + } + } + + protected ORID[] getRange() { + final ORID beginRange; + final ORID endRange; + + final OSQLFilterCondition rootCondition = compiledFilter == null ? null : compiledFilter.getRootCondition(); + if (compiledFilter == null || rootCondition == null) { + if (request instanceof OSQLSynchQuery) + beginRange = ((OSQLSynchQuery) request).getNextPageRID(); + else + beginRange = null; + endRange = null; + } else { + final ORID conditionBeginRange = rootCondition.getBeginRidRange(); + final ORID conditionEndRange = rootCondition.getEndRidRange(); + final ORID nextPageRid; + + if (request instanceof OSQLSynchQuery) + nextPageRid = ((OSQLSynchQuery) request).getNextPageRID(); + else + nextPageRid = null; + + if (conditionBeginRange != null && nextPageRid != null) + beginRange = conditionBeginRange.compareTo(nextPageRid) > 0 ? conditionBeginRange : nextPageRid; + else if (conditionBeginRange != null) + beginRange = conditionBeginRange; + else + beginRange = nextPageRid; + + endRange = conditionEndRange; + } + + return new ORID[] { beginRange, endRange }; + } + + public Iterator getTarget() { + return target; + } + + public void setTarget(final Iterator target) { + this.target = target; + } + + public void setRequest(final OSQLAsynchQuery request) { + this.request = request; + } + + public void setParsedTarget(final OSQLTarget parsedTarget) { + this.parsedTarget = parsedTarget; + } + + public void setCompiledFilter(final OSQLFilter compiledFilter) { + this.compiledFilter = compiledFilter; + } + + @Override + public boolean isCacheable() { + return true; + } + + public Object mergeResults(Map results) throws Exception { + + if (results.isEmpty()) + return null; + + // TODO: DELEGATE MERGE AT EVERY COMMAND + final ArrayList mergedResult = new ArrayList(); + + final Object firstResult = results.values().iterator().next(); + + for (Map.Entry entry : results.entrySet()) { + final String nodeName = entry.getKey(); + final Object nodeResult = entry.getValue(); + + if (nodeResult instanceof Collection) + mergedResult.addAll((Collection) nodeResult); + else if (nodeResult instanceof Exception) + // RECEIVED EXCEPTION + throw (Exception) nodeResult; + else + mergedResult.add(nodeResult); + } + + Object result = null; + + if (firstResult instanceof OResultSet) { + // REUSE THE SAME RESULTSET TO AVOID DUPLICATES + ((OResultSet) firstResult).clear(); + ((OResultSet) firstResult).addAll(mergedResult); + result = firstResult; + } else + result = new ArrayList(mergedResult); + + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetDelegate.java new file mode 100644 index 00000000000..77b3f104a2c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLResultsetDelegate.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Iterator; +import java.util.Map; + +/** + * SQL UPDATE command. + * + * @author Luca Garulli + * + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLResultsetDelegate extends OCommandExecutorSQLDelegate implements OIterableRecordSource, + Iterable { + + @Override + public Iterator iterator() { + return ((OIterableRecordSource) delegate).iterator(null); + } + + @Override + public Iterator iterator(final Map iArgs) { + return ((OIterableRecordSource) delegate).iterator(iArgs); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRetryAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRetryAbstract.java new file mode 100644 index 00000000000..1b83282d166 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRetryAbstract.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +/** + * Base abstract class with RETRY + * + * @author Luca Garulli + */ +public abstract class OCommandExecutorSQLRetryAbstract extends OCommandExecutorSQLSetAware { + public static final String KEYWORD_RETRY = "RETRY"; + + protected int retry = 1; + protected int wait = 0; + + /** + * Parses the RETRY number of times + */ + protected void parseRetry() throws OCommandSQLParsingException { + retry = Integer.parseInt(parserNextWord(true)); + + String temp = parseOptionalWord(true); + + if (temp.equals("WAIT")) { + wait = Integer.parseInt(parserNextWord(true)); + } else + parserGoBack(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRevoke.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRevoke.java new file mode 100644 index 00000000000..fb083239a9c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLRevoke.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.security.ORole; + +import java.util.Map; + +/** + * SQL REVOKE command: Revoke a privilege to a database role. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLRevoke extends OCommandExecutorSQLPermissionAbstract { + public static final String KEYWORD_REVOKE = "REVOKE"; + private static final String KEYWORD_FROM = "FROM"; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLRevoke parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + privilege = ORole.PERMISSION_NONE; + resource = null; + role = null; + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_REVOKE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_REVOKE + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid privilege", parserText, oldPos); + + parsePrivilege(word, oldPos); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_ON)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_ON + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserText, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid resource", parserText, oldPos); + + resource = word.toString(); + + pos = nextWord(parserText, parserTextUpperCase, pos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_FROM)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_FROM + " not found. Use " + getSyntax(), parserText, oldPos); + + pos = nextWord(parserText, parserText, pos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Invalid role", parserText, oldPos); + + final String roleName = word.toString(); + role = database.getMetadata().getSecurity().getRole(roleName); + if (role == null) + throw new OCommandSQLParsingException("Invalid role: " + roleName); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the command. + */ + public Object execute(final Map iArgs) { + if (role == null) + throw new OCommandExecutionException("Cannot execute the command because it has not yet been parsed"); + + role.revoke(resource, privilege); + role.save(); + + return role; + } + + public String getSyntax() { + return "REVOKE ON FROM "; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSelect.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSelect.java new file mode 100644 index 00000000000..52448598281 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSelect.java @@ -0,0 +1,2929 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.collection.OSortedMultiIterator; +import com.orientechnologies.common.concur.resource.OSharedResource; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.common.util.OPatternConst; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OBasicCommandContext; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.OExecutionThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.hook.ORecordHook; +import com.orientechnologies.orient.core.id.OContextualRecordId; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.iterator.OIdentifiableIterator; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorCluster; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClusters; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.metadata.schema.OImmutableSchema; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.metadata.security.OSecurityShared; +import com.orientechnologies.orient.core.metadata.security.OSecurityUser; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.*; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.functions.coll.OSQLFunctionDistinct; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionCount; +import com.orientechnologies.orient.core.sql.operator.*; +import com.orientechnologies.orient.core.sql.parser.*; +import com.orientechnologies.orient.core.sql.query.OResultSet; +import com.orientechnologies.orient.core.sql.query.OSQLQuery; +import com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Executes the SQL SELECT statement. the parse() method compiles the query and builds the meta information needed by the execute(). + * If the query contains the ORDER BY clause, the results are temporary collected internally, then ordered and finally returned all + * together to the listener. + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLSelect extends OCommandExecutorSQLResultsetAbstract implements OTemporaryRidGenerator { + public static final String KEYWORD_SELECT = "SELECT"; + public static final String KEYWORD_ASC = "ASC"; + public static final String KEYWORD_DESC = "DESC"; + public static final String KEYWORD_ORDER = "ORDER"; + public static final String KEYWORD_BY = "BY"; + public static final String KEYWORD_GROUP = "GROUP"; + public static final String KEYWORD_UNWIND = "UNWIND"; + public static final String KEYWORD_FETCHPLAN = "FETCHPLAN"; + public static final String KEYWORD_NOCACHE = "NOCACHE"; + private static final String KEYWORD_AS = "AS"; + private static final String KEYWORD_PARALLEL = "PARALLEL"; + private static final int PARTIAL_SORT_BUFFER_THRESHOLD = 10000; + + private static class AsyncResult { + final OIdentifiable record; + final OCommandContext context; + + public AsyncResult(final ORecord iRecord, final OCommandContext iContext) { + record = iRecord; + context = iContext; + } + } + + private static final AsyncResult PARALLEL_END_EXECUTION_THREAD = new AsyncResult(null, null); + + private final OOrderByOptimizer orderByOptimizer = new OOrderByOptimizer(); + private final OMetricRecorder metricRecorder = new OMetricRecorder(); + private final OFilterOptimizer filterOptimizer = new OFilterOptimizer(); + private final OFilterAnalyzer filterAnalyzer = new OFilterAnalyzer(); + private Map projectionDefinition = null; + // THIS HAS BEEN KEPT FOR COMPATIBILITY; BUT IT'S USED THE PROJECTIONS IN GROUPED-RESULTS + private Map projections = null; + private List> orderedFields = new ArrayList>(); + private List groupByFields; + private ConcurrentHashMap groupedResult = new ConcurrentHashMap(); + private boolean aggregate = false; + private List unwindFields; + private Object expandTarget; + private int fetchLimit = -1; + private OIdentifiable lastRecord; + private String fetchPlan; + private boolean fullySortedByIndex = false; + private LOCKING_STRATEGY lockingStrategy = LOCKING_STRATEGY.DEFAULT; + + private Boolean isAnyFunctionAggregates = null; + private volatile boolean parallel = false; + private volatile boolean parallelRunning; + private final ArrayBlockingQueue resultQueue = new ArrayBlockingQueue( + OGlobalConfiguration.QUERY_PARALLEL_RESULT_QUEUE_SIZE.getValueAsInteger()); + + private ConcurrentHashMap uniqueResult; + private boolean noCache = false; + private int tipLimitThreshold = OGlobalConfiguration.QUERY_LIMIT_THRESHOLD_TIP.getValueAsInteger(); + private String NULL_VALUE = "null"; + + private AtomicLong tmpQueueOffer = new AtomicLong(); + private Object resultLock = new Object(); + + public OCommandExecutorSQLSelect() { + } + + private static final class IndexUsageLog { + OIndex index; + List keyParams; + OIndexDefinition indexDefinition; + + IndexUsageLog(OIndex index, List keyParams, OIndexDefinition indexDefinition) { + this.index = index; + this.keyParams = keyParams; + this.indexDefinition = indexDefinition; + } + } + + private final class IndexComparator implements Comparator> { + public int compare(final OIndex indexOne, final OIndex indexTwo) { + final OIndexDefinition definitionOne = indexOne.getDefinition(); + final OIndexDefinition definitionTwo = indexTwo.getDefinition(); + + final int firstParamCount = definitionOne.getParamCount(); + final int secondParamCount = definitionTwo.getParamCount(); + + final int result = firstParamCount - secondParamCount; + + if (result == 0 && !orderedFields.isEmpty()) { + if (!(indexOne instanceof OChainedIndexProxy) && orderByOptimizer + .canBeUsedByOrderBy(indexOne, OCommandExecutorSQLSelect.this.orderedFields)) { + return 1; + } + + if (!(indexTwo instanceof OChainedIndexProxy) && orderByOptimizer + .canBeUsedByOrderBy(indexTwo, OCommandExecutorSQLSelect.this.orderedFields)) { + return -1; + } + } + String classNameOne = definitionOne.getClassName(); + String classNameTwo = definitionTwo.getClassName(); + if (classNameOne != null && classNameTwo != null) { + ODatabaseDocumentInternal db = getDatabase(); + OClass classOne = db.getMetadata().getSchema().getClass(classNameOne); + OClass classTwo = db.getMetadata().getSchema().getClass(classNameTwo); + + if (classOne.isSubClassOf(classTwo) && !classOne.equals(classTwo)) { + return -1; + } + if (classTwo.isSubClassOf(classOne) && !classOne.equals(classTwo)) { + return 1; + } + } + return result; + } + } + + private static Object getIndexKey(final OIndexDefinition indexDefinition, Object value, OCommandContext context) { + if (indexDefinition instanceof OCompositeIndexDefinition || indexDefinition.getParamCount() > 1) { + if (value instanceof List) { + final List values = (List) value; + List keyParams = new ArrayList(values.size()); + + for (Object o : values) { + keyParams.add(OSQLHelper.getValue(o, null, context)); + } + return indexDefinition.createValue(keyParams); + } else { + value = OSQLHelper.getValue(value); + if (value instanceof OCompositeKey) { + return value; + } else { + return indexDefinition.createValue(value); + } + } + } else { + if (indexDefinition instanceof OIndexDefinitionMultiValue) + return ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(OSQLHelper.getValue(value)); + else + return indexDefinition.createValue(OSQLHelper.getValue(value, null, context)); + } + } + + public boolean hasGroupBy() { + return groupByFields != null && groupByFields.size() > 0; + } + + @Override + protected boolean isUseCache() { + return !noCache && request.isUseCache(); + } + + private static ODocument createIndexEntryAsDocument(final Object iKey, final OIdentifiable iValue) { + final ODocument doc = new ODocument().setOrdered(true); + doc.field("key", iKey); + doc.field("rid", iValue); + ORecordInternal.unsetDirty(doc); + return doc; + } + + /** + * Compile the filter conditions only the first time. + */ + public OCommandExecutorSQLSelect parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + // System.out.println("NEW PARSER FROM: " + queryText); + queryText = preParse(queryText, iRequest); + // System.out.println("NEW PARSER TO: " + queryText); + textRequest.setText(queryText); + + super.parse(iRequest); + + initContext(); + + final int pos = parseProjections(); + if (pos == -1) { + return this; + } + + final int endPosition = parserText.length(); + + parserNextWord(true); + if (parserGetLastWord().equalsIgnoreCase(KEYWORD_FROM)) { + // FROM + parsedTarget = OSQLEngine.getInstance() + .parseTarget(parserText.substring(parserGetCurrentPosition(), endPosition), getContext()); + parserSetCurrentPosition( + parsedTarget.parserIsEnded() ? endPosition : parsedTarget.parserGetCurrentPosition() + parserGetCurrentPosition()); + } else { + parserGoBack(); + } + + if (!parserIsEnded()) { + parserSkipWhiteSpaces(); + + while (!parserIsEnded()) { + final String w = parserNextWord(true); + + if (!w.isEmpty()) { + if (w.equals(KEYWORD_WHERE)) { + compiledFilter = OSQLEngine.getInstance() + .parseCondition(parserText.substring(parserGetCurrentPosition(), endPosition), getContext(), KEYWORD_WHERE); + optimize(); + parserSetCurrentPosition(compiledFilter.parserIsEnded() ? + endPosition : + compiledFilter.parserGetCurrentPosition() + parserGetCurrentPosition()); + } else if (w.equals(KEYWORD_LET)) { + parseLet(); + } else if (w.equals(KEYWORD_GROUP)) { + parseGroupBy(); + } else if (w.equals(KEYWORD_ORDER)) { + parseOrderBy(); + } else if (w.equals(KEYWORD_UNWIND)) { + parseUnwind(); + } else if (w.equals(KEYWORD_LIMIT)) { + parseLimit(w); + } else if (w.equals(KEYWORD_SKIP) || w.equals(KEYWORD_OFFSET)) { + parseSkip(w); + } else if (w.equals(KEYWORD_FETCHPLAN)) { + parseFetchplan(w); + } else if (w.equals(KEYWORD_NOCACHE)) { + parseNoCache(w); + } else if (w.equals(KEYWORD_TIMEOUT)) { + parseTimeout(w); + } else if (w.equals(KEYWORD_LOCK)) { + final String lock = parseLock(); + + if (lock.equalsIgnoreCase("DEFAULT")) { + lockingStrategy = LOCKING_STRATEGY.DEFAULT; + } else if (lock.equals("NONE")) { + lockingStrategy = LOCKING_STRATEGY.NONE; + } else if (lock.equals("RECORD")) { + lockingStrategy = LOCKING_STRATEGY.EXCLUSIVE_LOCK; + } else if (lock.equals("SHARED")) { + lockingStrategy = LOCKING_STRATEGY.SHARED_LOCK; + } + } else if (w.equals(KEYWORD_PARALLEL)) { + parallel = parseParallel(w); + } else { + if (preParsedStatement == null) { + throwParsingException("Invalid keyword '" + w + "'"); + }//if the pre-parsed statement is OK, then you can go on with the rest, the SQL is valid and this is probably a space in a backtick + } + } + } + } + if (limit == 0 || limit < -1) { + throw new IllegalArgumentException("Limit must be > 0 or = -1 (no limit)"); + } + validateQuery(); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + private void validateQuery() { + if (this.let != null) { + for (Object letValue : let.values()) { + if (letValue instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) letValue; + if (f.getFunction().aggregateResults() && this.groupByFields != null && this.groupByFields.size() > 0) { + throwParsingException("Aggregate function cannot be used in LET clause together with GROUP BY"); + } + } + } + } + } + + /** + * Determine clusters that are used in select operation + * + * @return set of involved cluster names + */ + @Override + public Set getInvolvedClusters() { + + final Set clusters = new HashSet(); + + if (parsedTarget != null) { + final ODatabaseDocument db = getDatabase(); + + if (parsedTarget.getTargetQuery() != null && parsedTarget + .getTargetRecords() instanceof OCommandExecutorSQLResultsetDelegate) { + // SUB-QUERY: EXECUTE IT LOCALLY + // SUB QUERY, PROPAGATE THE CALL + final Set clIds = ((OCommandExecutorSQLResultsetDelegate) parsedTarget.getTargetRecords()).getInvolvedClusters(); + for (String c : clIds) { + // FILTER THE CLUSTER WHERE THE USER HAS THE RIGHT ACCESS + if (checkClusterAccess(db, c)) { + clusters.add(c); + } + } + + } else if (parsedTarget.getTargetRecords() != null) { + // SINGLE RECORDS: BROWSE ALL (COULD BE EXPENSIVE). + for (OIdentifiable identifiable : parsedTarget.getTargetRecords()) { + final String c = db.getClusterNameById(identifiable.getIdentity().getClusterId()).toLowerCase(Locale.ENGLISH); + // FILTER THE CLUSTER WHERE THE USER HAS THE RIGHT ACCESS + if (checkClusterAccess(db, c)) { + clusters.add(c); + } + } + } + + if (parsedTarget.getTargetClasses() != null) { + return getInvolvedClustersOfClasses(parsedTarget.getTargetClasses().values()); + } + + if (parsedTarget.getTargetClusters() != null) { + return getInvolvedClustersOfClusters(parsedTarget.getTargetClusters().keySet()); + } + + if (parsedTarget.getTargetIndex() != null) { + // EXTRACT THE CLASS NAME -> CLUSTERS FROM THE INDEX DEFINITION + return getInvolvedClustersOfIndex(parsedTarget.getTargetIndex()); + } + + } + return clusters; + } + + /** + * @return {@code ture} if any of the sql functions perform aggregation, {@code false} otherwise + */ + public boolean isAnyFunctionAggregates() { + if (isAnyFunctionAggregates == null) { + if (projections != null) { + for (Entry p : projections.entrySet()) { + if (p.getValue() instanceof OSQLFunctionRuntime && ((OSQLFunctionRuntime) p.getValue()).aggregateResults()) { + isAnyFunctionAggregates = true; + break; + } + } + } + + if (isAnyFunctionAggregates == null) + isAnyFunctionAggregates = false; + } + return isAnyFunctionAggregates; + } + + public Iterator iterator() { + return iterator(null); + } + + public Iterator iterator(final Map iArgs) { + if (compiledFilter != null) { + mergeRangeConditionsToBetweenOperators(compiledFilter); + } + + final Iterator subIterator; + if (target == null) { + // GET THE RESULT + executeSearch(iArgs); + applyExpand(); + handleNoTarget(); + handleGroupBy(context); + applyOrderBy(true); + applyLimitAndSkip(); + + subIterator = new ArrayList((List) getResult()).iterator(); + lastRecord = null; + tempResult = null; + groupedResult.clear(); + aggregate = false; + } else { + subIterator = (Iterator) target; + } + + return subIterator; + } + + public Object execute(final Map iArgs) { + bindDefaultContextVariables(); + + if (iArgs != null) + // BIND ARGUMENTS INTO CONTEXT TO ACCESS FROM ANY POINT (EVEN FUNCTIONS) + { + for (Entry arg : iArgs.entrySet()) { + context.setVariable(arg.getKey().toString(), arg.getValue()); + } + } + + if (timeoutMs > 0) { + getContext().beginExecution(timeoutMs, timeoutStrategy); + } + + if (!optimizeExecution()) { + fetchLimit = getQueryFetchLimit(); + + executeSearch(iArgs); + applyExpand(); + handleNoTarget(); + handleGroupBy(context); + applyOrderBy(true); + applyLimitAndSkip(); + } + return getResult(); + } + + public Map getProjections() { + return projections; + } + + @Override + public String getSyntax() { + return "SELECT [] FROM [LET *] [WHERE *] [ORDER BY * [ASC|DESC]*] [LIMIT ] [TIMEOUT ] [LOCK none|record] [NOCACHE]"; + } + + public String getFetchPlan() { + return fetchPlan != null ? fetchPlan : request.getFetchPlan(); + } + + protected void executeSearch(final Map iArgs) { + assignTarget(iArgs); + + if (target == null) { + if (let != null) + // EXECUTE ONCE TO ASSIGN THE LET + { + assignLetClauses(lastRecord != null ? lastRecord.getRecord() : null); + } + + // SEARCH WITHOUT USING TARGET (USUALLY WHEN LET/INDEXES ARE INVOLVED) + return; + } + + fetchFromTarget(target); + } + + @Override + protected boolean assignTarget(Map iArgs) { + if (!super.assignTarget(iArgs)) { + if (parsedTarget.getTargetIndex() != null) { + searchInIndex(); + } else { + throw new OQueryParsingException( + "No source found in query: specify class, cluster(s), index or single record(s). Use " + getSyntax()); + } + } + return true; + } + + protected boolean executeSearchRecord(final OIdentifiable id, final OCommandContext iContext, boolean callHooks) { + if (id == null) + return false; + + final ORID identity = id.getIdentity(); + + if (uniqueResult != null) { + if (uniqueResult.containsKey(identity)) + return true; + + if (identity.isValid()) + uniqueResult.put(identity, identity); + } + + if (!checkInterruption()) + return false; + + final LOCKING_STRATEGY contextLockingStrategy = + iContext.getVariable("$locking") != null ? (LOCKING_STRATEGY) iContext.getVariable("$locking") : null; + + final LOCKING_STRATEGY localLockingStrategy = contextLockingStrategy != null ? contextLockingStrategy : lockingStrategy; + + if (localLockingStrategy != null && !(localLockingStrategy == LOCKING_STRATEGY.DEFAULT + || localLockingStrategy == LOCKING_STRATEGY.NONE || localLockingStrategy == LOCKING_STRATEGY.EXCLUSIVE_LOCK + || localLockingStrategy == LOCKING_STRATEGY.SHARED_LOCK)) + throw new IllegalStateException("Unsupported locking strategy " + localLockingStrategy); + + if (localLockingStrategy == LOCKING_STRATEGY.SHARED_LOCK) { + id.lock(false); + + if (id instanceof ORecord) { + final ORecord record = (ORecord) id; + record.reload(null, true, false); + } + + } else if (localLockingStrategy == LOCKING_STRATEGY.EXCLUSIVE_LOCK) { + id.lock(true); + + if (id instanceof ORecord) { + final ORecord record = (ORecord) id; + record.reload(null, true, false); + } + } + + ORecord record = null; + try { + if (!(id instanceof ORecord)) { + record = getDatabase().load(id.getIdentity(), null, !isUseCache()); + if (id instanceof OContextualRecordId && ((OContextualRecordId) id).getContext() != null) { + Map ridContext = ((OContextualRecordId) id).getContext(); + for (String key : ridContext.keySet()) { + context.setVariable(key, ridContext.get(key)); + } + } + } else { + record = (ORecord) id; + } + + iContext.updateMetric("recordReads", +1); + + if (record == null) + // SKIP IT + return true; + if (ORecordInternal.getRecordType(record) != ODocument.RECORD_TYPE && checkSkipBlob()) + // SKIP binary records in case of projection. + return true; + + iContext.updateMetric("documentReads", +1); + + iContext.setVariable("current", record); + + if (filter(record, iContext)) { + if (callHooks) { + ((ODatabaseDocumentInternal) getDatabase()).callbackHooks(ORecordHook.TYPE.BEFORE_READ, record); + ((ODatabaseDocumentInternal) getDatabase()).callbackHooks(ORecordHook.TYPE.AFTER_READ, record); + } + + if (parallel) { + try { + applyGroupBy(record, iContext); + resultQueue.put(new AsyncResult(record, iContext)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + tmpQueueOffer.incrementAndGet(); + } else { + applyGroupBy(record, iContext); + + if (!handleResult(record, iContext)) { + // LIMIT REACHED + return false; + } + } + } + } finally { + + if (localLockingStrategy != null && record != null && record.isLocked()) { + // CONTEXT LOCK: lock must be released (no matter if filtered or not) + if (localLockingStrategy == LOCKING_STRATEGY.EXCLUSIVE_LOCK || localLockingStrategy == LOCKING_STRATEGY.SHARED_LOCK) { + record.unlock(); + } + } + } + + return true; + } + + private boolean checkSkipBlob() { + if (expandTarget != null) + return true; + return false; + } + + /** + * Handles the record in result. + * + * @param iRecord Record to handle + * @param iContext + * + * @return false if limit has been reached, otherwise true + */ + @Override + protected boolean handleResult(final OIdentifiable iRecord, final OCommandContext iContext) { + lastRecord = iRecord; + + if ((orderedFields.isEmpty() || fullySortedByIndex || isRidOnlySort()) && skip > 0 && this.unwindFields == null + && this.expandTarget == null) { + lastRecord = null; + skip--; + return true; + } + + if (!addResult(lastRecord, iContext)) { + return false; + } + + return continueSearching(); + } + + private boolean continueSearching() { + return !((orderedFields.isEmpty() || fullySortedByIndex || isRidOnlySort()) && !isAnyFunctionAggregates() && ( + groupByFields == null || groupByFields.isEmpty()) && fetchLimit > -1 && resultCount >= fetchLimit && expandTarget == null); + } + + /** + * Returns the temporary RID counter assuring it's unique per query tree. + * + * @param iContext + * + * @return Serial as integer + */ + public int getTemporaryRIDCounter(final OCommandContext iContext) { + final OTemporaryRidGenerator parentQuery = (OTemporaryRidGenerator) iContext.getVariable("parentQuery"); + return parentQuery != null && parentQuery != this ? + parentQuery.getTemporaryRIDCounter(iContext) : + serialTempRID.getAndIncrement(); + } + + protected void checkForSystemClusters(final ODatabaseDocumentInternal iDatabase, final int[] iClusterIds) { + for (int clId : iClusterIds) { + if (clId < 0) { + continue; + } + final com.orientechnologies.orient.core.storage.OCluster cl = iDatabase.getStorage().getClusterById(clId); + if (cl != null && cl.isSystemCluster()) { + final OSecurityUser dbUser = iDatabase.getUser(); + if (dbUser == null || dbUser.allow(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_READ) != null) + // AUTHORIZED + break; + } + } + } + + protected boolean addResult(OIdentifiable iRecord, final OCommandContext iContext) { + resultCount++; + if (iRecord == null) + return true; + + checkForSystemClusters(getDatabase(), new int[] { iRecord.getIdentity().getClusterId() }); + if (projections != null || groupByFields != null && !groupByFields.isEmpty()) { + if (!aggregate) { + // APPLY PROJECTIONS IN LINE + + iRecord = ORuntimeResult.getProjectionResult(getTemporaryRIDCounter(iContext), projections, iContext, iRecord); + if (iRecord == null) { + resultCount--;// record discarded + return true; + } + + } else { + // GROUP BY + return true; + } + } + + if (tipLimitThreshold > 0 && resultCount > tipLimitThreshold && getLimit() == -1) { + reportTip(String.format( + "Query '%s' returned a result set with more than %d records. Check if you really need all these records, or reduce the resultset by using a LIMIT to improve both performance and used RAM", + parserText, tipLimitThreshold)); + tipLimitThreshold = 0; + } + + List allResults = new ArrayList(); + if (unwindFields != null) { + Collection partial = unwind(iRecord, this.unwindFields, iContext); + + for (OIdentifiable item : partial) { + allResults.add(item); + } + } else { + allResults.add(iRecord); + } + boolean result = true; + if (allowsStreamedResult()) { + // SEND THE RESULT INLINE + if (request.getResultListener() != null) + for (OIdentifiable iRes : allResults) { + result = pushResult(iRes); + } + } else { + + // COLLECT ALL THE RECORDS AND ORDER THEM AT THE END + if (tempResult == null) + tempResult = new ArrayList(); + + applyPartialOrderBy(); + + for (OIdentifiable iRes : allResults) { + ((Collection) tempResult).add(iRes); + } + } + + return result; + } + + private ODocument applyGroupBy(final OIdentifiable iRecord, final OCommandContext iContext) { + if (!aggregate) + return null; + + // AGGREGATION/GROUP BY + Object fieldValue = null; + if (groupByFields != null && !groupByFields.isEmpty()) { + if (groupByFields.size() > 1) { + // MULTI-FIELD GROUP BY + final ODocument doc = iRecord.getRecord(); + final Object[] fields = new Object[groupByFields.size()]; + for (int i = 0; i < groupByFields.size(); ++i) { + final String field = groupByFields.get(i); + if (field.startsWith("$")) + fields[i] = iContext.getVariable(field); + else + fields[i] = doc.field(field); + + } + fieldValue = fields; + } else { + final String field = groupByFields.get(0); + if (field != null) { + if (field.startsWith("$")) + fieldValue = iContext.getVariable(field); + else + fieldValue = ((ODocument) iRecord.getRecord()).field(field); + } + } + } + + return getProjectionGroup(fieldValue, iContext).applyRecord(iRecord); + } + + private boolean allowsStreamedResult() { + return (fullySortedByIndex || orderedFields.isEmpty()) && expandTarget == null && unwindFields == null; + } + + /** + * in case of ORDER BY + SKIP + LIMIT, this method applies ORDER BY operation on partial result and discards overflowing results + * (results > skip + limit) + */ + private void applyPartialOrderBy() { + if (expandTarget != null || (unwindFields != null && unwindFields.size() > 0) || orderedFields.isEmpty() || fullySortedByIndex + || isRidOnlySort()) { + return; + } + + if (limit > 0) { + int sortBufferSize = limit + 1; + if (skip > 0) { + sortBufferSize += skip; + } + if (tempResult instanceof List && ((List) tempResult).size() >= sortBufferSize + PARTIAL_SORT_BUFFER_THRESHOLD) { + applyOrderBy(false); + tempResult = new ArrayList(((List) tempResult).subList(0, sortBufferSize)); + } + } + } + + private Collection unwind(final OIdentifiable iRecord, final List unwindFields, + final OCommandContext iContext) { + final List result = new ArrayList(); + ODocument doc; + if (iRecord instanceof ODocument) { + doc = (ODocument) iRecord; + } else { + doc = iRecord.getRecord(); + } + if (unwindFields.size() == 0) { + ORecordInternal.setIdentity(doc, new ORecordId(-2, getTemporaryRIDCounter(iContext))); + result.add(doc); + } else { + String firstField = unwindFields.get(0); + final List nextFields = unwindFields.subList(1, unwindFields.size()); + + Object fieldValue = doc.field(firstField); + if (fieldValue == null || !(fieldValue instanceof Iterable) || fieldValue instanceof ODocument) { + result.addAll(unwind(doc, nextFields, iContext)); + } else { + Iterator iterator = ((Iterable) fieldValue).iterator(); + if (!iterator.hasNext()) { + ODocument unwindedDoc = new ODocument(); + doc.copyTo(unwindedDoc); + unwindedDoc.field(firstField, (Object) null); + result.addAll(unwind(unwindedDoc, nextFields, iContext)); + } else { + do { + Object o = iterator.next(); + ODocument unwindedDoc = new ODocument(); + doc.copyTo(unwindedDoc); + unwindedDoc.field(firstField, o); + result.addAll(unwind(unwindedDoc, nextFields, iContext)); + } while (iterator.hasNext()); + } + } + } + return result; + } + + /** + * Report the tip to the profiler and collect it in context to be reported by tools like Studio + * + * @param iMessage + */ + protected void reportTip(final String iMessage) { + Orient.instance().getProfiler().reportTip(iMessage); + List tips = (List) context.getVariable("tips"); + if (tips == null) { + tips = new ArrayList(3); + context.setVariable("tips", tips); + } + tips.add(iMessage); + } + + protected ORuntimeResult getProjectionGroup(final Object fieldValue, final OCommandContext iContext) { + final long projectionElapsed = (Long) context.getVariable("projectionElapsed", 0l); + final long begin = System.currentTimeMillis(); + try { + + aggregate = true; + + Object key; + + if (fieldValue != null) { + if (fieldValue.getClass().isArray()) { + // LOOK IT BY HASH (FASTER THAN COMPARE EACH SINGLE VALUE) + final Object[] array = (Object[]) fieldValue; + + final StringBuilder keyArray = new StringBuilder(); + for (Object o : array) { + if (keyArray.length() > 0) { + keyArray.append(","); + } + if (o != null) { + keyArray.append(o instanceof OIdentifiable ? ((OIdentifiable) o).getIdentity().toString() : o.toString()); + } else { + keyArray.append(NULL_VALUE); + } + } + + key = keyArray.toString(); + } else { + // LOOKUP FOR THE FIELD + key = fieldValue; + } + } else + // USE NULL_VALUE THEN REPLACE WITH REAL NULL + key = NULL_VALUE; + + ORuntimeResult group = groupedResult.get(key); + if (group == null) { + group = new ORuntimeResult(fieldValue, createProjectionFromDefinition(), getTemporaryRIDCounter(iContext), context); + final ORuntimeResult prev = groupedResult.putIfAbsent(key, group); + if (prev != null) + // ALREADY EXISTENT: USE THIS + group = prev; + } + return group; + + } finally { + context.setVariable("projectionElapsed", projectionElapsed + (System.currentTimeMillis() - begin)); + } + } + + protected void parseGroupBy() { + parserRequiredKeyword(KEYWORD_BY); + + groupByFields = new ArrayList(); + while (!parserIsEnded() && (groupByFields.size() == 0 || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',')) { + final String fieldName = parserRequiredWord(false, "Field name expected"); + groupByFields.add(fieldName); + parserSkipWhiteSpaces(); + } + + if (groupByFields.size() == 0) { + throwParsingException("Group by field set was missed. Example: GROUP BY name, salary"); + } + + // AGGREGATE IT + aggregate = true; + groupedResult.clear(); + } + + protected void parseUnwind() { + unwindFields = new ArrayList(); + while (!parserIsEnded() && (unwindFields.size() == 0 || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',')) { + final String fieldName = parserRequiredWord(false, "Field name expected"); + unwindFields.add(fieldName); + parserSkipWhiteSpaces(); + } + + if (unwindFields.size() == 0) { + throwParsingException("unwind field set was missed. Example: UNWIND name, salary"); + } + } + + protected void parseOrderBy() { + parserRequiredKeyword(KEYWORD_BY); + + String fieldOrdering = null; + + orderedFields = new ArrayList>(); + while (!parserIsEnded() && (orderedFields.size() == 0 || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',')) { + final String fieldName = parserRequiredWord(false, "Field name expected"); + + parserOptionalWord(true); + + final String word = parserGetLastWord(); + + if (word.length() == 0) + // END CLAUSE: SET AS ASC BY DEFAULT + { + fieldOrdering = KEYWORD_ASC; + } else if (word.equals(KEYWORD_LIMIT) || word.equals(KEYWORD_SKIP) || word.equals(KEYWORD_OFFSET)) { + // NEXT CLAUSE: SET AS ASC BY DEFAULT + fieldOrdering = KEYWORD_ASC; + parserGoBack(); + } else { + if (word.equals(KEYWORD_ASC)) { + fieldOrdering = KEYWORD_ASC; + } else if (word.equals(KEYWORD_DESC)) { + fieldOrdering = KEYWORD_DESC; + } else { + throwParsingException("Ordering mode '" + word + "' not supported. Valid is 'ASC', 'DESC' or nothing ('ASC' by default)"); + } + } + + orderedFields.add(new OPair(fieldName, fieldOrdering)); + parserSkipWhiteSpaces(); + } + + if (orderedFields.size() == 0) { + throwParsingException("Order by field set was missed. Example: ORDER BY name ASC, salary DESC"); + } + } + + @Override + protected void searchInClasses() { + final String className = parsedTarget.getTargetClasses().keySet().iterator().next(); + + final OClass cls = getDatabase().getMetadata().getSchema().getClass(className); + if (!searchForIndexes(cls) && !searchForSubclassIndexes(cls)) { + // CHECK FOR INVERSE ORDER + final boolean browsingOrderAsc = isBrowsingAscendingOrder(); + super.searchInClasses(browsingOrderAsc); + } + } + + private boolean isBrowsingAscendingOrder() { + return !(orderedFields.size() == 1 && orderedFields.get(0).getKey().equalsIgnoreCase("@rid") && orderedFields.get(0).getValue() + .equalsIgnoreCase("DESC")); + } + + protected int parseProjections() { + if (!parserOptionalKeyword(KEYWORD_SELECT)) + return -1; + + int upperBound = OStringSerializerHelper + .getLowerIndexOfKeywords(parserTextUpperCase, parserGetCurrentPosition(), KEYWORD_FROM, KEYWORD_LET); + if (upperBound == -1) + // UP TO THE END + upperBound = parserText.length(); + + int lastRealPositionProjection = -1; + + int currPos = parserGetCurrentPosition(); + if (currPos == -1) + return -1; + + final String projectionString = parserText.substring(currPos, upperBound); + if (projectionString.trim().length() > 0) { + // EXTRACT PROJECTIONS + projections = new LinkedHashMap(); + projectionDefinition = new LinkedHashMap(); + + final List items = OStringSerializerHelper.smartSplit(projectionString, ','); + + int endPos; + for (String projectionItem : items) { + String projection = OStringSerializerHelper.smartTrim(projectionItem.trim(), true, true); + + if (projectionDefinition == null) + throw new OCommandSQLParsingException("Projection not allowed with FLATTEN() and EXPAND() operators"); + + final List words = OStringSerializerHelper.smartSplit(projection, ' '); + + String fieldName; + if (words.size() > 1 && words.get(1).trim().equalsIgnoreCase(KEYWORD_AS)) { + // FOUND AS, EXTRACT ALIAS + if (words.size() < 3) + throw new OCommandSQLParsingException("Found 'AS' without alias"); + + fieldName = words.get(2).trim(); + + if (projectionDefinition.containsKey(fieldName)) + throw new OCommandSQLParsingException( + "Field '" + fieldName + "' is duplicated in current SELECT, choose a different name"); + + projection = words.get(0).trim(); + + if (words.size() > 3) + lastRealPositionProjection = projectionString.indexOf(words.get(3)); + else + lastRealPositionProjection += projectionItem.length() + 1; + + } else { + // EXTRACT THE FIELD NAME WITHOUT FUNCTIONS AND/OR LINKS + projection = words.get(0); + fieldName = projection; + + lastRealPositionProjection = projectionString.indexOf(fieldName) + fieldName.length() + 1; + + if (fieldName.charAt(0) == '@') + fieldName = fieldName.substring(1); + + endPos = extractProjectionNameSubstringEndPosition(fieldName); + + if (endPos > -1) + fieldName = fieldName.substring(0, endPos); + + // FIND A UNIQUE NAME BY ADDING A COUNTER + for (int fieldIndex = 2; projectionDefinition.containsKey(fieldName); ++fieldIndex) + fieldName += fieldIndex; + } + + final String p = upperCase(projection); + if (p.startsWith("FLATTEN(") || p.startsWith("EXPAND(")) { + if (p.startsWith("FLATTEN(")) + OLogManager.instance().debug(this, "FLATTEN() operator has been replaced by EXPAND()"); + + List pars = OStringSerializerHelper.getParameters(projection); + if (pars.size() != 1) + throw new OCommandSQLParsingException( + "EXPAND/FLATTEN operators expects the field name as parameter. Example EXPAND( out )"); + + expandTarget = OSQLHelper.parseValue(this, pars.get(0).trim(), context); + + // BY PASS THIS AS PROJECTION BUT TREAT IT AS SPECIAL + projectionDefinition = null; + projections = null; + + if (!aggregate && expandTarget instanceof OSQLFunctionRuntime && ((OSQLFunctionRuntime) expandTarget).aggregateResults()) + aggregate = true; + + continue; + } + + fieldName = OIOUtils.getStringContent(fieldName); + + projectionDefinition.put(fieldName, projection); + } + + if (projectionDefinition != null && (projectionDefinition.size() > 1 || !projectionDefinition.values().iterator().next() + .equals("*"))) { + projections = createProjectionFromDefinition(); + + for (Object p : projections.values()) { + + if (!aggregate && p instanceof OSQLFunctionRuntime && ((OSQLFunctionRuntime) p).aggregateResults()) { + // AGGREGATE IT + getProjectionGroup(null, context); + break; + } + } + + } else { + // TREATS SELECT * AS NO PROJECTION + projectionDefinition = null; + projections = null; + } + } + + if (upperBound < parserText.length() - 1) + parserSetCurrentPosition(upperBound); + else if (lastRealPositionProjection > -1) + parserMoveCurrentPosition(lastRealPositionProjection); + else + parserSetEndOfText(); + + return parserGetCurrentPosition(); + } + + protected Map createProjectionFromDefinition() { + if (projectionDefinition == null) { + return new LinkedHashMap(); + } + + final Map projections = new LinkedHashMap(projectionDefinition.size()); + for (Entry p : projectionDefinition.entrySet()) { + final Object projectionValue = OSQLHelper.parseValue(this, p.getValue(), context); + projections.put(p.getKey(), projectionValue); + } + return projections; + } + + protected int extractProjectionNameSubstringEndPosition(final String projection) { + int endPos; + final int pos1 = projection.indexOf('.'); + final int pos2 = projection.indexOf('('); + final int pos3 = projection.indexOf('['); + if (pos1 > -1 && pos2 == -1 && pos3 == -1) { + endPos = pos1; + } else if (pos2 > -1 && pos1 == -1 && pos3 == -1) { + endPos = pos2; + } else if (pos3 > -1 && pos1 == -1 && pos2 == -1) { + endPos = pos3; + } else if (pos1 > -1 && pos2 > -1 && pos3 == -1) { + endPos = Math.min(pos1, pos2); + } else if (pos2 > -1 && pos3 > -1 && pos1 == -1) { + endPos = Math.min(pos2, pos3); + } else if (pos1 > -1 && pos3 > -1 && pos2 == -1) { + endPos = Math.min(pos1, pos3); + } else if (pos1 > -1 && pos2 > -1 && pos3 > -1) { + endPos = Math.min(pos1, pos2); + endPos = Math.min(endPos, pos3); + } else { + endPos = -1; + } + return endPos; + } + + /** + * Parses the fetchplan keyword if found. + */ + protected boolean parseFetchplan(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_FETCHPLAN)) { + return false; + } + + parserSkipWhiteSpaces(); + int start = parserGetCurrentPosition(); + + parserNextWord(true); + int end = parserGetCurrentPosition(); + parserSkipWhiteSpaces(); + + int position = parserGetCurrentPosition(); + while (!parserIsEnded()) { + final String word = OIOUtils.getStringContent(parserNextWord(true)); + if (!OPatternConst.PATTERN_FETCH_PLAN.matcher(word).matches()) { + break; + } + + end = parserGetCurrentPosition(); + parserSkipWhiteSpaces(); + position = parserGetCurrentPosition(); + } + + parserSetCurrentPosition(position); + + if (end < 0) { + fetchPlan = OIOUtils.getStringContent(parserText.substring(start)); + } else { + fetchPlan = OIOUtils.getStringContent(parserText.substring(start, end)); + } + + request.setFetchPlan(fetchPlan); + + return true; + } + + protected boolean optimizeExecution() { + if (compiledFilter != null) { + mergeRangeConditionsToBetweenOperators(compiledFilter); + } + + if ((compiledFilter == null || (compiledFilter.getRootCondition() == null)) && groupByFields == null && projections != null + && projections.size() == 1) { + + final long startOptimization = System.currentTimeMillis(); + try { + + final Entry entry = projections.entrySet().iterator().next(); + + if (entry.getValue() instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime rf = (OSQLFunctionRuntime) entry.getValue(); + if (rf.function instanceof OSQLFunctionCount && rf.configuredParameters.length == 1 && "*" + .equals(rf.configuredParameters[0])) { + + final boolean restrictedClasses = isUsingRestrictedClasses(); + + if (!restrictedClasses) { + long count = 0; + + if (parsedTarget.getTargetClasses() != null) { + final String className = parsedTarget.getTargetClasses().keySet().iterator().next(); + final OClass cls = getDatabase().getMetadata().getSchema().getClass(className); + count = cls.count(); + } else if (parsedTarget.getTargetClusters() != null) { + for (String cluster : parsedTarget.getTargetClusters().keySet()) { + count += getDatabase().countClusterElements(cluster); + } + } else if (parsedTarget.getTargetIndex() != null) { + count += getDatabase().getMetadata().getIndexManager().getIndex(parsedTarget.getTargetIndex()).getSize(); + } else { + final Iterable recs = parsedTarget.getTargetRecords(); + if (recs != null) { + if (recs instanceof Collection) + count += ((Collection) recs).size(); + else { + for (Object o : recs) + count++; + } + } + + } + + if (tempResult == null) + tempResult = new ArrayList(); + ((Collection) tempResult).add(new ODocument().field(entry.getKey(), count)); + return true; + } + } + } + + } finally { + context.setVariable("optimizationElapsed", (System.currentTimeMillis() - startOptimization)); + } + } + + return false; + } + + private boolean isUsingRestrictedClasses() { + boolean restrictedClasses = false; + final OSecurityUser user = getDatabase().getUser(); + + if (parsedTarget.getTargetClasses() != null && user != null + && user.checkIfAllowed(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_READ) == null) { + for (String className : parsedTarget.getTargetClasses().keySet()) { + final OClass cls = getDatabase().getMetadata().getSchema().getClass(className); + if (cls.isSubClassOf(OSecurityShared.RESTRICTED_CLASSNAME)) { + restrictedClasses = true; + break; + } + } + } + return restrictedClasses; + } + + protected void revertSubclassesProfiler(final OCommandContext iContext, int num) { + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + profiler.updateCounter(profiler.getDatabaseMetric(getDatabase().getName(), "query.indexUseAttemptedAndReverted"), + "Reverted index usage in query", num); + } + } + + protected void revertProfiler(final OCommandContext iContext, final OIndex index, final List keyParams, + final OIndexDefinition indexDefinition) { + if (iContext.isRecordingMetrics()) { + iContext.updateMetric("compositeIndexUsed", -1); + } + + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + profiler.updateCounter(profiler.getDatabaseMetric(index.getDatabaseName(), "query.indexUsed"), "Used index in query", -1); + + int params = indexDefinition.getParamCount(); + if (params > 1) { + final String profiler_prefix = profiler.getDatabaseMetric(index.getDatabaseName(), "query.compositeIndexUsed"); + + profiler.updateCounter(profiler_prefix, "Used composite index in query", -1); + profiler.updateCounter(profiler_prefix + "." + params, "Used composite index in query with " + params + " params", -1); + profiler.updateCounter(profiler_prefix + "." + params + '.' + keyParams.size(), + "Used composite index in query with " + params + " params and " + keyParams.size() + " keys", -1); + } + } + } + + /** + * Parses the NOCACHE keyword if found. + */ + protected boolean parseNoCache(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_NOCACHE)) + return false; + + noCache = true; + return true; + } + + private void mergeRangeConditionsToBetweenOperators(OSQLFilter filter) { + OSQLFilterCondition condition = filter.getRootCondition(); + + OSQLFilterCondition newCondition = convertToBetweenClause(condition); + if (newCondition != null) { + filter.setRootCondition(newCondition); + metricRecorder.recordRangeQueryConvertedInBetween(); + return; + } + + mergeRangeConditionsToBetweenOperators(condition); + } + + private void mergeRangeConditionsToBetweenOperators(OSQLFilterCondition condition) { + if (condition == null) { + return; + } + + OSQLFilterCondition newCondition; + + if (condition.getLeft() instanceof OSQLFilterCondition) { + OSQLFilterCondition leftCondition = (OSQLFilterCondition) condition.getLeft(); + newCondition = convertToBetweenClause(leftCondition); + + if (newCondition != null) { + condition.setLeft(newCondition); + metricRecorder.recordRangeQueryConvertedInBetween(); + } else { + mergeRangeConditionsToBetweenOperators(leftCondition); + } + } + + if (condition.getRight() instanceof OSQLFilterCondition) { + OSQLFilterCondition rightCondition = (OSQLFilterCondition) condition.getRight(); + + newCondition = convertToBetweenClause(rightCondition); + if (newCondition != null) { + condition.setRight(newCondition); + metricRecorder.recordRangeQueryConvertedInBetween(); + } else { + mergeRangeConditionsToBetweenOperators(rightCondition); + } + } + } + + private OSQLFilterCondition convertToBetweenClause(final OSQLFilterCondition condition) { + if (condition == null) { + return null; + } + + final Object right = condition.getRight(); + final Object left = condition.getLeft(); + + final OQueryOperator operator = condition.getOperator(); + if (!(operator instanceof OQueryOperatorAnd)) { + return null; + } + + if (!(right instanceof OSQLFilterCondition)) { + return null; + } + + if (!(left instanceof OSQLFilterCondition)) { + return null; + } + + String rightField; + + final OSQLFilterCondition rightCondition = (OSQLFilterCondition) right; + final OSQLFilterCondition leftCondition = (OSQLFilterCondition) left; + + if (rightCondition.getLeft() instanceof OSQLFilterItemField && rightCondition.getRight() instanceof OSQLFilterItemField) { + return null; + } + + if (!(rightCondition.getLeft() instanceof OSQLFilterItemField) && !(rightCondition.getRight() instanceof OSQLFilterItemField)) { + return null; + } + + if (leftCondition.getLeft() instanceof OSQLFilterItemField && leftCondition.getRight() instanceof OSQLFilterItemField) { + return null; + } + + if (!(leftCondition.getLeft() instanceof OSQLFilterItemField) && !(leftCondition.getRight() instanceof OSQLFilterItemField)) { + return null; + } + + final List betweenBoundaries = new ArrayList(); + + if (rightCondition.getLeft() instanceof OSQLFilterItemField) { + final OSQLFilterItemField itemField = (OSQLFilterItemField) rightCondition.getLeft(); + if (!itemField.isFieldChain()) { + return null; + } + + if (itemField.getFieldChain().getItemCount() > 1) { + return null; + } + + rightField = itemField.getRoot(); + betweenBoundaries.add(rightCondition.getRight()); + } else if (rightCondition.getRight() instanceof OSQLFilterItemField) { + final OSQLFilterItemField itemField = (OSQLFilterItemField) rightCondition.getRight(); + if (!itemField.isFieldChain()) { + return null; + } + + if (itemField.getFieldChain().getItemCount() > 1) { + return null; + } + + rightField = itemField.getRoot(); + betweenBoundaries.add(rightCondition.getLeft()); + } else { + return null; + } + + betweenBoundaries.add("and"); + + String leftField; + if (leftCondition.getLeft() instanceof OSQLFilterItemField) { + final OSQLFilterItemField itemField = (OSQLFilterItemField) leftCondition.getLeft(); + if (!itemField.isFieldChain()) { + return null; + } + + if (itemField.getFieldChain().getItemCount() > 1) { + return null; + } + + leftField = itemField.getRoot(); + betweenBoundaries.add(leftCondition.getRight()); + } else if (leftCondition.getRight() instanceof OSQLFilterItemField) { + final OSQLFilterItemField itemField = (OSQLFilterItemField) leftCondition.getRight(); + if (!itemField.isFieldChain()) { + return null; + } + + if (itemField.getFieldChain().getItemCount() > 1) { + return null; + } + + leftField = itemField.getRoot(); + betweenBoundaries.add(leftCondition.getLeft()); + } else { + return null; + } + + if (!leftField.equalsIgnoreCase(rightField)) { + return null; + } + + final OQueryOperator rightOperator = ((OSQLFilterCondition) right).getOperator(); + final OQueryOperator leftOperator = ((OSQLFilterCondition) left).getOperator(); + + if ((rightOperator instanceof OQueryOperatorMajor || rightOperator instanceof OQueryOperatorMajorEquals) && ( + leftOperator instanceof OQueryOperatorMinor || leftOperator instanceof OQueryOperatorMinorEquals)) { + + final OQueryOperatorBetween between = new OQueryOperatorBetween(); + + if (rightOperator instanceof OQueryOperatorMajor) { + between.setLeftInclusive(false); + } + + if (leftOperator instanceof OQueryOperatorMinor) { + between.setRightInclusive(false); + } + + return new OSQLFilterCondition(new OSQLFilterItemField(this, leftField, null), between, betweenBoundaries.toArray()); + } + + if ((leftOperator instanceof OQueryOperatorMajor || leftOperator instanceof OQueryOperatorMajorEquals) && ( + rightOperator instanceof OQueryOperatorMinor || rightOperator instanceof OQueryOperatorMinorEquals)) { + final OQueryOperatorBetween between = new OQueryOperatorBetween(); + + if (leftOperator instanceof OQueryOperatorMajor) { + between.setLeftInclusive(false); + } + + if (rightOperator instanceof OQueryOperatorMinor) { + between.setRightInclusive(false); + } + + Collections.reverse(betweenBoundaries); + + return new OSQLFilterCondition(new OSQLFilterItemField(this, leftField, null), between, betweenBoundaries.toArray()); + + } + + return null; + } + + public void initContext() { + if (context == null) { + context = new OBasicCommandContext(); + } + + metricRecorder.setContext(context); + } + + private boolean fetchFromTarget(final Iterator iTarget) { + fetchLimit = getQueryFetchLimit(); + + final long startFetching = System.currentTimeMillis(); + + final int[] clusterIds = iTarget instanceof ORecordIteratorClusters ? + ((ORecordIteratorClusters) iTarget).getClusterIds() : + null; + + parallel = (parallel || OGlobalConfiguration.QUERY_PARALLEL_AUTO.getValueAsBoolean()) && canRunParallel(clusterIds, iTarget); + + try { + if (parallel) + return parallelExec(iTarget); + + boolean prefetchRecords = false; + + if (canScanStorageCluster(clusterIds)) { + prefetchRecords = true; + } + + final ODatabaseDocumentInternal database = getDatabase(); + database.setPrefetchRecords(prefetchRecords); + try { + // WORK WITH ITERATOR + return serialIterator(iTarget); + } finally { + database.setPrefetchRecords(false); + } + + } finally { + context.setVariable("fetchingFromTargetElapsed", (System.currentTimeMillis() - startFetching)); + } + } + + private boolean canRunParallel(int[] clusterIds, Iterator iTarget) { + if (getDatabase().getTransaction().isActive()) + return false; + + if (iTarget instanceof ORecordIteratorClusters) { + if (clusterIds.length > 1) { + final long totalRecords = getDatabase().getStorage().count(clusterIds); + if (totalRecords > OGlobalConfiguration.QUERY_PARALLEL_MINIMUM_RECORDS.getValueAsLong()) { + // ACTIVATE PARALLEL + OLogManager.instance() + .debug(this, "Activated parallel query. clusterIds=%d, totalRecords=%d", clusterIds.length, totalRecords); + return true; + } + } + } + return false; + } + + private boolean canScanStorageCluster(final int[] clusterIds) { + final ODatabaseDocumentInternal db = getDatabase(); + + if (clusterIds != null && request.isIdempotent() && !db.getTransaction().isActive()) { + final OImmutableSchema schema = ((OMetadataInternal) db.getMetadata()).getImmutableSchemaSnapshot(); + for (int clusterId : clusterIds) { + final OImmutableClass cls = (OImmutableClass) schema.getClassByClusterId(clusterId); + if (cls != null) { + if (cls.isRestricted() || cls.isOuser() || cls.isOrole()) + return false; + } + } + return true; + } + return false; + } + + private boolean serialIterator(Iterator iTarget) { + int queryScanThresholdWarning = OGlobalConfiguration.QUERY_SCAN_THRESHOLD_TIP.getValueAsInteger(); + + boolean tipActivated = queryScanThresholdWarning > 0 && iTarget instanceof OIdentifiableIterator && compiledFilter != null; + + // BROWSE, UNMARSHALL AND FILTER ALL THE RECORDS ON CURRENT THREAD + for (int browsed = 0; iTarget.hasNext(); browsed++) { + final OIdentifiable next = iTarget.next(); + if (!executeSearchRecord(next, context, false)) + return false; + } + return true; + } + + private boolean parseParallel(String w) { + return w.equals(KEYWORD_PARALLEL); + } + + private boolean parallelExec(final Iterator iTarget) { + final OResultSet result = (OResultSet) getResultInstance(); + + // BROWSE ALL THE RECORDS ON CURRENT THREAD BUT DELEGATE UNMARSHALLING AND FILTER TO A THREAD POOL + final ODatabaseDocumentInternal db = getDatabase(); + + if (limit > -1) { + if (result != null) { + result.setLimit(limit); + } + } + + final boolean res = execParallelWithPool((ORecordIteratorClusters) iTarget, (ODatabaseDocumentTx) db); + + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Parallel query '%s' completed", parserText); + + return res; + } + + private boolean execParallelWithPool(final ORecordIteratorClusters iTarget, final ODatabaseDocumentTx db) { + final int[] clusterIds = iTarget.getClusterIds(); + + // CREATE ONE THREAD PER CLUSTER + final int jobNumbers = clusterIds.length; + final List> jobs = new ArrayList>(); + + OLogManager.instance() + .debug(this, "Executing parallel query with strategy executors. clusterIds=%d, jobs=%d", clusterIds.length, jobNumbers); + + final boolean[] results = new boolean[jobNumbers]; + final OCommandContext[] contexts = new OCommandContext[jobNumbers]; + + final RuntimeException[] exceptions = new RuntimeException[jobNumbers]; + + parallelRunning = true; + + final AtomicInteger runningJobs = new AtomicInteger(jobNumbers); + + for (int i = 0; i < jobNumbers; ++i) { + final int current = i; + + final Runnable job = new Runnable() { + @Override + public void run() { + try { + ODatabaseDocumentInternal localDatabase = null; + try { + exceptions[current] = null; + results[current] = true; + + final OCommandContext threadContext = context.copy(); + contexts[current] = threadContext; + + localDatabase = db.copy(); + localDatabase.activateOnCurrentThread(); + + // CREATE A SNAPSHOT TO AVOID DEADLOCKS + db.getMetadata().getSchema().makeSnapshot(); + + scanClusterWithIterator(localDatabase, threadContext, clusterIds[current], current, results); + } catch (RuntimeException t) { + exceptions[current] = t; + } finally { + runningJobs.decrementAndGet(); + resultQueue.offer(PARALLEL_END_EXECUTION_THREAD); + + if (localDatabase != null) + localDatabase.close(); + + } + } catch (Exception e) { + if (exceptions[current] == null) { + exceptions[current] = new RuntimeException(e); + } + e.printStackTrace(); + } + } + }; + + jobs.add(Orient.instance().submit(job)); + } + + final int maxQueueSize = OGlobalConfiguration.QUERY_PARALLEL_RESULT_QUEUE_SIZE.getValueAsInteger() - 1; + + boolean cancelQuery = false; + boolean tipProvided = false; + while (runningJobs.get() > 0 || !resultQueue.isEmpty()) { + try { + final AsyncResult result = resultQueue.take(); + + final int qSize = resultQueue.size(); + + if (!tipProvided && qSize >= maxQueueSize) { + OLogManager.instance().debug(this, + "Parallel query '%s' has result queue full (size=%d), this could reduce concurrency level. Consider increasing queue size with setting: %s=", + parserText, maxQueueSize + 1, OGlobalConfiguration.QUERY_PARALLEL_RESULT_QUEUE_SIZE.getKey()); + tipProvided = true; + } + + if (OExecutionThreadLocal.isInterruptCurrentOperation()) + throw new InterruptedException("Operation has been interrupted"); + + if (result != PARALLEL_END_EXECUTION_THREAD) { + + if (!handleResult(result.record, result.context)) { + // STOP EXECUTORS + parallelRunning = false; + break; + } + } + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + cancelQuery = true; + break; + } + } + + parallelRunning = false; + + if (cancelQuery) { + // CANCEL ALL THE RUNNING JOBS + for (int i = 0; i < jobs.size(); ++i) { + jobs.get(i).cancel(true); + } + } else { + // JOIN ALL THE JOBS + for (int i = 0; i < jobs.size(); ++i) { + try { + jobs.get(i).get(); + context.merge(contexts[i]); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } catch (final ExecutionException e) { + OLogManager.instance().error(this, "Error on executing parallel query", e); + throw OException.wrapException(new OCommandExecutionException("Error on executing parallel query"), e); + } + } + } + + // CHECK FOR ANY EXCEPTION + for (int i = 0; i < jobNumbers; ++i) + if (exceptions[i] != null) + throw exceptions[i]; + + for (int i = 0; i < jobNumbers; ++i) { + if (!results[i]) + return false; + } + return true; + } + + private void scanClusterWithIterator(final ODatabaseDocumentInternal localDatabase, final OCommandContext iContext, + final int iClusterId, final int current, final boolean[] results) { + final ORecordIteratorCluster it = new ORecordIteratorCluster(localDatabase, localDatabase, iClusterId); + + while (it.hasNext()) { + final ORecord next = it.next(); + + if (!executeSearchRecord(next, iContext, false)) { + results[current] = false; + break; + } + + if (parallel && !parallelRunning) + // EXECUTION ENDED + break; + } + } + + private int getQueryFetchLimit() { + final int sqlLimit; + final int requestLimit; + + if (limit > -1) { + sqlLimit = limit; + } else { + sqlLimit = -1; + } + + if (request.getLimit() > -1) { + requestLimit = request.getLimit(); + } else { + requestLimit = -1; + } + + if (sqlLimit == -1) { + return requestLimit; + } + + if (requestLimit == -1) { + return sqlLimit; + } + + return Math.min(sqlLimit, requestLimit); + } + + private OIndexCursor tryGetOptimizedSortCursor(final OClass iSchemaClass) { + if (orderedFields.size() == 0) { + return null; + } else { + return getOptimizedSortCursor(iSchemaClass); + } + } + + private boolean tryOptimizeSort(final OClass iSchemaClass) { + if (orderedFields.size() == 0) { + return false; + } else { + return optimizeSort(iSchemaClass); + } + } + + private boolean searchForSubclassIndexes(final OClass iSchemaClass) { + Collection subclasses = iSchemaClass.getSubclasses(); + if (subclasses.size() == 0) { + return false; + } + + final OOrderBy order = new OOrderBy(); + order.setItems(new ArrayList()); + if (this.orderedFields != null) { + for (OPair pair : this.orderedFields) { + OOrderByItem item = new OOrderByItem(); + item.setRecordAttr(pair.getKey()); + if (pair.getValue() == null) { + item.setType(OOrderByItem.ASC); + } else { + item.setType(pair.getValue().toUpperCase(Locale.ENGLISH).equals("DESC") ? OOrderByItem.DESC : OOrderByItem.ASC); + } + order.getItems().add(item); + } + } + OSortedMultiIterator cursor = new OSortedMultiIterator(order); + boolean fullySorted = true; + + if (!iSchemaClass.isAbstract()) { + Iterator parentClassIterator = (Iterator) searchInClasses(iSchemaClass, false, true); + if (parentClassIterator.hasNext()) { + cursor.add(parentClassIterator); + fullySorted = false; + } + } + + if (uniqueResult != null) { + uniqueResult.clear(); + } + + int attempted = 0; + for (OClass subclass : subclasses) { + List subcursors = getIndexCursors(subclass); + fullySorted = fullySorted && fullySortedByIndex; + if (subcursors == null || subcursors.size() == 0) { + if (attempted > 0) { + revertSubclassesProfiler(context, attempted); + } + return false; + } + for (OIndexCursor c : subcursors) { + if (!fullySortedByIndex) { + // TODO sort every iterator + } + attempted++; + cursor.add(c); + } + + } + fullySortedByIndex = fullySorted; + + uniqueResult = new ConcurrentHashMap(); + + fetchFromTarget(cursor); + + if (uniqueResult != null) { + uniqueResult.clear(); + } + uniqueResult = null; + + return true; + } + + @SuppressWarnings("rawtypes") + private List getIndexCursors(final OClass iSchemaClass) { + + final ODatabaseDocument database = getDatabase(); + +// Leaving this in for reference, for the moment. +// This should not be necessary as searchInClasses() does a security check and when the record iterator +// calls OClassImpl.readableClusters(), it too filters out clusters based on the class's security permissions. +// This throws an unnecessary exception that potentially prevents using an index and prevents filtering later. +// database.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, iSchemaClass.getName().toLowerCase(Locale.ENGLISH)); + + // fetch all possible variants of subqueries that can be used in indexes. + if (compiledFilter == null) { + OIndexCursor cursor = tryGetOptimizedSortCursor(iSchemaClass); + if (cursor == null) { + return null; + } + List result = new ArrayList(); + result.add(cursor); + return result; + + } + + // the main condition is a set of sub-conditions separated by OR operators + final List> conditionHierarchy = filterAnalyzer + .analyzeMainCondition(compiledFilter.getRootCondition(), iSchemaClass, context); + if (conditionHierarchy == null) + return null; + + List cursors = new ArrayList(); + + boolean indexIsUsedInOrderBy = false; + + OIndexSearchResult lastSearchResult = null; + for (List indexSearchResults : conditionHierarchy) { + // go through all variants to choose which one can be used for index search. + boolean indexUsed = false; + for (final OIndexSearchResult searchResult : indexSearchResults) { + lastSearchResult = searchResult; + final List> involvedIndexes = filterAnalyzer.getInvolvedIndexes(iSchemaClass, searchResult); + + Collections.sort(involvedIndexes, new IndexComparator()); + + // go through all possible index for given set of fields. + for (final OIndex index : involvedIndexes) { + final long indexRebuildVersion = index.getRebuildVersion(); + + if (index.isRebuilding()) { + continue; + } + + final OIndexDefinition indexDefinition = index.getDefinition(); + + if (searchResult.containsNullValues && indexDefinition.isNullValuesIgnored()) { + continue; + } + + final OQueryOperator operator = searchResult.lastOperator; + + // we need to test that last field in query subset and field in index that has the same position + // are equals. + if (!OIndexSearchResult.isIndexEqualityOperator(operator)) { + final String lastFiled = searchResult.lastField.getItemName(searchResult.lastField.getItemCount() - 1); + final String relatedIndexField = indexDefinition.getFields().get(searchResult.fieldValuePairs.size()); + if (!lastFiled.equals(relatedIndexField)) { + continue; + } + } + + final int searchResultFieldsCount = searchResult.fields().size(); + final List keyParams = new ArrayList(searchResultFieldsCount); + // We get only subset contained in processed sub query. + for (final String fieldName : indexDefinition.getFields().subList(0, searchResultFieldsCount)) { + final Object fieldValue = searchResult.fieldValuePairs.get(fieldName); + if (fieldValue instanceof OSQLQuery) { + return null; + } + + if (fieldValue != null) { + keyParams.add(fieldValue); + } else { + if (searchResult.lastValue instanceof OSQLQuery) { + return null; + } + + keyParams.add(searchResult.lastValue); + } + } + + metricRecorder.recordInvolvedIndexesMetric(index); + + OIndexCursor cursor; + indexIsUsedInOrderBy = + orderByOptimizer.canBeUsedByOrderBy(index, orderedFields) && !(index.getInternal() instanceof OChainedIndexProxy); + try { + boolean ascSortOrder = !indexIsUsedInOrderBy || orderedFields.get(0).getValue().equals(KEYWORD_ASC); + + if (indexIsUsedInOrderBy) { + fullySortedByIndex = expandTarget == null && indexDefinition.getFields().size() >= orderedFields.size() + && conditionHierarchy.size() == 1; + } + + context.setVariable("$limit", limit); + + cursor = operator.executeIndexQuery(context, index, keyParams, ascSortOrder); + + } catch (OIndexEngineException e) { + throw e; + } catch (Exception e) { + OLogManager.instance().error(this, + "Error on using index %s in query '%s'. Probably you need to rebuild indexes. Now executing query using cluster scan", + e, index.getName(), request != null && request.getText() != null ? request.getText() : ""); + + fullySortedByIndex = false; + cursors.clear(); + return null; + } + + if (cursor == null) { + continue; + } + + if (indexRebuildVersion == index.getRebuildVersion()) { + cursors.add(OIndexChangesWrapper.wrap(index, cursor, indexRebuildVersion)); + indexUsed = true; + break; + } + } + if (indexUsed) { + break; + } + } + if (!indexUsed) { + OIndexCursor cursor = tryGetOptimizedSortCursor(iSchemaClass); + if (cursor == null) { + return null; + } + List result = new ArrayList(); + result.add(cursor); + return result; + } + } + + if (cursors.size() == 0 || lastSearchResult == null) { + return null; + } + + metricRecorder.recordOrderByOptimizationMetric(indexIsUsedInOrderBy, this.fullySortedByIndex); + + return cursors; + } + + @SuppressWarnings("rawtypes") + private boolean searchForIndexes(final OClass iSchemaClass) { + if (uniqueResult != null) + uniqueResult.clear(); + + final ODatabaseDocument database = getDatabase(); + database.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, iSchemaClass.getName().toLowerCase(Locale.ENGLISH)); + + // fetch all possible variants of subqueries that can be used in indexes. + if (compiledFilter == null) { + return tryOptimizeSort(iSchemaClass); + } + + // try indexed functions + Iterator fetchedFromFunction = tryIndexedFunctions(iSchemaClass); + if (fetchedFromFunction != null) { + fetchFromTarget(fetchedFromFunction); + return true; + } + + // the main condition is a set of sub-conditions separated by OR operators + final List> conditionHierarchy = filterAnalyzer + .analyzeMainCondition(compiledFilter.getRootCondition(), iSchemaClass, context); + if (conditionHierarchy == null) + return false; + + List cursors = new ArrayList(); + + boolean indexIsUsedInOrderBy = false; + List indexUseAttempts = new ArrayList(); + try { + + boolean indexOnExactClass = true;//to track if the index used is specific for this class or if it's defined on a super/sub class + + OIndexSearchResult lastSearchResult = null; + for (List indexSearchResults : conditionHierarchy) { + // go through all variants to choose which one can be used for index search. + boolean indexUsed = false; + for (final OIndexSearchResult searchResult : indexSearchResults) { + lastSearchResult = searchResult; + final List> involvedIndexes = filterAnalyzer.getInvolvedIndexes(iSchemaClass, searchResult); + + Collections.sort(involvedIndexes, new IndexComparator()); + + indexOnExactClass = true; + + // go through all possible index for given set of fields. + for (final OIndex index : involvedIndexes) { + final long indexRebuildVersion = index.getRebuildVersion(); + + if (index.isRebuilding()) { + continue; + } + + final OIndexDefinition indexDefinition = index.getDefinition(); + + if (searchResult.containsNullValues && indexDefinition.isNullValuesIgnored()) { + continue; + } + + final OQueryOperator operator = searchResult.lastOperator; + + // we need to test that last field in query subset and field in index that has the same position + // are equals. + if (!OIndexSearchResult.isIndexEqualityOperator(operator)) { + final String lastFiled = searchResult.lastField.getItemName(searchResult.lastField.getItemCount() - 1); + final String relatedIndexField = indexDefinition.getFields().get(searchResult.fieldValuePairs.size()); + if (!lastFiled.equals(relatedIndexField)) { + continue; + } + } + + final int searchResultFieldsCount = searchResult.fields().size(); + final List keyParams = new ArrayList(searchResultFieldsCount); + // We get only subset contained in processed sub query. + for (final String fieldName : indexDefinition.getFields().subList(0, searchResultFieldsCount)) { + Object fieldValue = searchResult.fieldValuePairs.get(fieldName); + if (fieldValue instanceof OSQLQuery || fieldValue instanceof OSQLFilterCondition) { + return false; + } + + if (fieldValue != null) { + keyParams.add(fieldValue); + } else { + if (searchResult.lastValue instanceof OSQLQuery || searchResult.lastValue instanceof OSQLFilterCondition) { + return false; + } + + keyParams.add(searchResult.lastValue); + } + } + + OIndexCursor cursor; + + indexIsUsedInOrderBy = + orderByOptimizer.canBeUsedByOrderByAfterFilter(index, getEqualsClausesPrefix(searchResult), orderedFields) + && !(index.getInternal() instanceof OChainedIndexProxy); + try { + boolean ascSortOrder = !indexIsUsedInOrderBy || orderedFields.get(0).getValue().equals(KEYWORD_ASC); + + if (indexIsUsedInOrderBy) { + fullySortedByIndex = expandTarget == null && indexDefinition.getFields().size() >= orderedFields.size() + && conditionHierarchy.size() == 1; + } + + context.setVariable("$limit", limit); + + cursor = operator.executeIndexQuery(context, index, keyParams, ascSortOrder); + if (cursor != null) { + metricRecorder.recordInvolvedIndexesMetric(index); + } + + if (!iSchemaClass.getName().equals(index.getDefinition().getClassName())) { + indexOnExactClass = false; + } + } catch (OIndexEngineException e) { + throw e; + } catch (Exception e) { + OLogManager.instance().error(this, + "Error on using index %s in query '%s'. Probably you need to rebuild indexes. Now executing query using cluster scan", + e, index.getName(), request != null && request.getText() != null ? request.getText() : ""); + + fullySortedByIndex = false; + cursors.clear(); + return false; + } + + if (cursor == null) { + continue; + } + + if (index.getRebuildVersion() == indexRebuildVersion) { + cursors.add(OIndexChangesWrapper.wrap(index, cursor, indexRebuildVersion)); + indexUseAttempts.add(new IndexUsageLog(index, keyParams, indexDefinition)); + indexUsed = true; + break; + } + } + if (indexUsed) { + break; + } + } + if (!indexUsed) { + return tryOptimizeSort(iSchemaClass); + } + } + + if (cursors.size() == 0 || lastSearchResult == null) { + return false; + } + + if (cursors.size() == 1 && canOptimize(conditionHierarchy)) { + filterOptimizer.optimize(compiledFilter, lastSearchResult); + } + + uniqueResult = new ConcurrentHashMap(); + + if (cursors.size() == 1 && (compiledFilter == null || compiledFilter.getRootCondition() == null) && groupByFields == null + && projections != null && projections.size() == 1) { + // OPTIMIZATION: ONE INDEX USED WITH JUST ONE CONDITION: REMOVE THE FILTER + final Entry entry = projections.entrySet().iterator().next(); + + if (entry.getValue() instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime rf = (OSQLFunctionRuntime) entry.getValue(); + if (rf.function instanceof OSQLFunctionCount && rf.configuredParameters.length == 1 && "*" + .equals(rf.configuredParameters[0])) { + + final boolean restrictedClasses = isUsingRestrictedClasses(); + + if (!restrictedClasses && indexOnExactClass) { + final OIndexCursor cursor = cursors.get(0); + long count = 0; + if (cursor instanceof OSizeable) + count = ((OSizeable) cursor).size(); + else { + while (cursor.hasNext()) { + cursor.next(); + count++; + } + } + + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + profiler + .updateCounter(profiler.getDatabaseMetric(database.getName(), "query.indexUsed"), "Used index in query", +1); + } + if (tempResult == null) + tempResult = new ArrayList(); + ((Collection) tempResult).add(new ODocument().field(entry.getKey(), count)); + return true; + } + } + } + } + + for (OIndexCursor cursor : cursors) { + if (!fetchValuesFromIndexCursor(cursor)) { + break; + } + } + uniqueResult.clear(); + uniqueResult = null; + + metricRecorder.recordOrderByOptimizationMetric(indexIsUsedInOrderBy, this.fullySortedByIndex); + + indexUseAttempts.clear(); + return true; + } finally { + for (IndexUsageLog wastedIndexUsage : indexUseAttempts) { + revertProfiler(context, wastedIndexUsage.index, wastedIndexUsage.keyParams, wastedIndexUsage.indexDefinition); + } + } + } + + private Iterator tryIndexedFunctions(OClass iSchemaClass) { + // TODO profiler + if (this.preParsedStatement == null) { + return null; + } + OWhereClause where = ((OSelectStatement) this.preParsedStatement).getWhereClause(); + if (where == null) { + return null; + } + List conditions = where.getIndexedFunctionConditions(iSchemaClass, getDatabase()); + + long lastEstimation = Long.MAX_VALUE; + OBinaryCondition bestCondition = null; + if (conditions == null) { + return null; + } + for (OBinaryCondition condition : conditions) { + long estimation = condition.estimateIndexed(((OSelectStatement) this.preParsedStatement).getTarget(), getContext()); + if (estimation > -1 && estimation < lastEstimation) { + lastEstimation = estimation; + bestCondition = condition; + } + } + + if (bestCondition == null) { + return null; + } + Iterable result = bestCondition + .executeIndexedFunction(((OSelectStatement) this.preParsedStatement).getTarget(), getContext()); + if (result == null) { + return null; + } + return result.iterator(); + } + + private List getEqualsClausesPrefix(OIndexSearchResult searchResult) { + List result = new ArrayList(); + if (searchResult.lastOperator instanceof OQueryOperatorEquals) { + return searchResult.fields(); + } else { + return searchResult.fields().subList(0, searchResult.fields().size() - 1); + } + + } + + private boolean canOptimize(List> conditionHierarchy) { + if (conditionHierarchy.size() > 1) { + return false; + } + for (List subCoditions : conditionHierarchy) { + if (subCoditions.size() > 1) { + return false; + } + } + return true; + } + + /** + * Use index to order documents by provided fields. + * + * @param iSchemaClass where search for indexes for optimization. + * + * @return true if execution was optimized + */ + private boolean optimizeSort(OClass iSchemaClass) { + OIndexCursor cursor = getOptimizedSortCursor(iSchemaClass); + if (cursor != null) { + fetchValuesFromIndexCursor(cursor); + return true; + } + return false; + } + + private OIndexCursor getOptimizedSortCursor(OClass iSchemaClass) { + final List fieldNames = new ArrayList(); + + for (OPair pair : orderedFields) { + fieldNames.add(pair.getKey()); + } + + final Set> indexes = iSchemaClass.getInvolvedIndexes(fieldNames); + + for (OIndex index : indexes) { + if (orderByOptimizer.canBeUsedByOrderBy(index, orderedFields)) { + final long indexRebuildVersion = index.getRebuildVersion(); + + if (index.getDefinition().isNullValuesIgnored()) { + return null; + } + if (index.isRebuilding()) + return null; + + final boolean ascSortOrder = orderedFields.get(0).getValue().equals(KEYWORD_ASC); + + final Object key; + if (ascSortOrder) { + key = index.getFirstKey(); + } else { + key = index.getLastKey(); + } + + if (index.getKeySize() == 0) { + return null; + } + + final List cursors = new ArrayList(); + + OIndexCursor cursor = null; + + if (key != null) { + if (ascSortOrder) { + cursor = index.iterateEntriesMajor(key, true, true); + } else { + cursor = index.iterateEntriesMinor(key, true, false); + } + } + + if (cursor != null) + cursors.add(OIndexChangesWrapper.wrap(index, cursor, indexRebuildVersion)); + + if (index.getMetadata() != null && !index.getDefinition().isNullValuesIgnored()) { + Object nullValue = index.get(null); + if (nullValue != null) { + if (nullValue instanceof Collection) + cursors.add(OIndexChangesWrapper + .wrap(index, new OIndexCursorCollectionValue((Collection) nullValue, null), indexRebuildVersion)); + else + cursors.add(OIndexChangesWrapper + .wrap(index, new OIndexCursorSingleValue((OIdentifiable) nullValue, null), indexRebuildVersion)); + } + } + + if (indexRebuildVersion == index.getRebuildVersion()) { + fullySortedByIndex = true; + + if (context.isRecordingMetrics()) { + context.setVariable("indexIsUsedInOrderBy", true); + context.setVariable("fullySortedByIndex", fullySortedByIndex); + + Set idxNames = (Set) context.getVariable("involvedIndexes"); + if (idxNames == null) { + idxNames = new HashSet(); + context.setVariable("involvedIndexes", idxNames); + } + + idxNames.add(index.getName()); + } + + return new OCompositeIndexCursor(cursors); + } else { + return null; + } + } + } + + metricRecorder.recordOrderByOptimizationMetric(false, this.fullySortedByIndex); + return null; + } + + private boolean fetchValuesFromIndexCursor(final OIndexCursor cursor) { + int needsToFetch; + if (fetchLimit > 0) { + needsToFetch = fetchLimit + skip; + } else { + needsToFetch = -1; + } + + cursor.setPrefetchSize(needsToFetch); + return fetchFromTarget(cursor); + } + + private void fetchEntriesFromIndexCursor(final OIndexCursor cursor) { + int needsToFetch; + if (fetchLimit > 0) { + needsToFetch = fetchLimit + skip; + } else { + needsToFetch = -1; + } + + cursor.setPrefetchSize(needsToFetch); + + Entry entryRecord = cursor.nextEntry(); + if (needsToFetch > 0) { + needsToFetch--; + } + + while (entryRecord != null) { + final ODocument doc = new ODocument().setOrdered(true); + doc.field("key", entryRecord.getKey()); + doc.field("rid", entryRecord.getValue().getIdentity()); + ORecordInternal.unsetDirty(doc); + + applyGroupBy(doc, context); + + if (!handleResult(doc, context)) { + // LIMIT REACHED + break; + } + + if (needsToFetch > 0) { + needsToFetch--; + cursor.setPrefetchSize(needsToFetch); + } + + entryRecord = cursor.nextEntry(); + } + } + + private boolean isRidOnlySort() { + if (parsedTarget.getTargetClasses() != null && this.orderedFields.size() == 1 && this.orderedFields.get(0).getKey() + .toLowerCase(Locale.ENGLISH).equals("@rid")) { + if (this.target != null && target instanceof ORecordIteratorClass) { + return true; + } + } + return false; + } + + private void applyOrderBy(boolean clearOrderedFields) { + if (orderedFields.isEmpty() || fullySortedByIndex || isRidOnlySort()) { + return; + } + + final long startOrderBy = System.currentTimeMillis(); + try { + if (tempResult instanceof OMultiCollectionIterator) { + final List list = new ArrayList(); + for (OIdentifiable o : tempResult) { + list.add(o); + } + tempResult = list; + } + tempResult = applySort((List) tempResult, orderedFields, context); + if (clearOrderedFields) { + orderedFields.clear(); + } + } finally { + metricRecorder.orderByElapsed(startOrderBy); + } + } + + private Iterable applySort(List iCollection, List> iOrderFields, + OCommandContext iContext) { + + ODocumentHelper.sort(iCollection, iOrderFields, iContext); + return iCollection; + } + + /** + * Extract the content of collections and/or links and put it as result + */ + private void applyExpand() { + if (expandTarget == null) { + return; + } + + final long startExpand = System.currentTimeMillis(); + try { + + if (tempResult == null) { + tempResult = new ArrayList(); + if (expandTarget instanceof OSQLFilterItemVariable) { + Object r = ((OSQLFilterItemVariable) expandTarget).getValue(null, null, context); + if (r != null) { + if (r instanceof OIdentifiable) { + ((Collection) tempResult).add((OIdentifiable) r); + } else if (r instanceof Iterator || OMultiValue.isMultiValue(r)) { + for (Object o : OMultiValue.getMultiValueIterable(r)) { + ((Collection) tempResult).add((OIdentifiable) o); + } + } + } + } else if (expandTarget instanceof OSQLFunctionRuntime && !hasFieldItemParams((OSQLFunctionRuntime) expandTarget)) { + if (((OSQLFunctionRuntime) expandTarget).aggregateResults()) { + throw new OCommandExecutionException("Unsupported operation: aggregate function in expand(" + expandTarget + ")"); + } else { + Object r = ((OSQLFunctionRuntime) expandTarget).execute(null, null, null, context); + if (r instanceof OIdentifiable) { + ((Collection) tempResult).add((OIdentifiable) r); + } else if (r instanceof Iterator || OMultiValue.isMultiValue(r)) { + int i = 0; + for (Object o : OMultiValue.getMultiValueIterable(r)) { + if ((++i) % 100 == 0 && !checkInterruption()) { + return; + } + ((Collection) tempResult).add((OIdentifiable) o); + } + } + } + } + } else { + if (tempResult == null) { + tempResult = new ArrayList(); + } + final OMultiCollectionIterator finalResult = new OMultiCollectionIterator(); + + if (orderedFields == null || orderedFields.size() == 0) { + // expand is applied before sorting, so limiting the result set here would give wrong results + int iteratorLimit = 0; + if (limit < 0) { + iteratorLimit = -1; + } else { + iteratorLimit += limit; + } + finalResult.setLimit(iteratorLimit); + finalResult.setSkip(skip); + } + + for (OIdentifiable id : tempResult) { + if (!checkInterruption()) { + return; + } + final Object fieldValue; + if (expandTarget instanceof OSQLFilterItem) { + fieldValue = ((OSQLFilterItem) expandTarget).getValue(id.getRecord(), null, context); + } else if (expandTarget instanceof OSQLFunctionRuntime) { + fieldValue = ((OSQLFunctionRuntime) expandTarget).getResult(); + } else { + fieldValue = expandTarget.toString(); + } + + if (fieldValue != null) { + if (fieldValue instanceof ODocument) { + ArrayList partial = new ArrayList(); + partial.add((ODocument) fieldValue); + finalResult.add(partial); + } else if (fieldValue instanceof Collection || fieldValue.getClass().isArray() || fieldValue instanceof Iterator + || fieldValue instanceof OIdentifiable || fieldValue instanceof ORidBag) { + finalResult.add(fieldValue); + } else if (fieldValue instanceof Map) { + finalResult.add(((Map) fieldValue).values()); + } + } + } + tempResult = finalResult; + } + } finally { + context.setVariable("expandElapsed", (System.currentTimeMillis() - startExpand)); + } + + } + + private boolean hasFieldItemParams(OSQLFunctionRuntime expandTarget) { + Object[] params = expandTarget.getConfiguredParameters(); + if (params == null) { + return false; + } + for (Object o : params) { + if (o instanceof OSQLFilterItemField) { + return true; + } + } + return false; + } + + private void searchInIndex() { + final OIndex index = (OIndex) getDatabase().getMetadata().getIndexManager() + .getIndex(parsedTarget.getTargetIndex()); + + if (index == null) { + throw new OCommandExecutionException("Target index '" + parsedTarget.getTargetIndex() + "' not found"); + } + + boolean ascOrder = true; + if (!orderedFields.isEmpty()) { + if (orderedFields.size() != 1) { + throw new OCommandExecutionException("Index can be ordered only by key field"); + } + + final String fieldName = orderedFields.get(0).getKey(); + if (!fieldName.equalsIgnoreCase("key")) { + throw new OCommandExecutionException("Index can be ordered only by key field"); + } + + final String order = orderedFields.get(0).getValue(); + ascOrder = order.equalsIgnoreCase(KEYWORD_ASC); + } + + // nothing was added yet, so index definition for manual index was not calculated + if (index.getDefinition() == null) { + return; + } + + if (compiledFilter != null && compiledFilter.getRootCondition() != null) { + if (!"KEY".equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString())) { + throw new OCommandExecutionException("'Key' field is required for queries against indexes"); + } + + final OQueryOperator indexOperator = compiledFilter.getRootCondition().getOperator(); + + if (indexOperator instanceof OQueryOperatorBetween) { + final Object[] values = (Object[]) compiledFilter.getRootCondition().getRight(); + + final OIndexCursor cursor = index.iterateEntriesBetween(getIndexKey(index.getDefinition(), values[0], context), true, + getIndexKey(index.getDefinition(), values[2], context), true, ascOrder); + fetchEntriesFromIndexCursor(cursor); + } else if (indexOperator instanceof OQueryOperatorMajor) { + final Object value = compiledFilter.getRootCondition().getRight(); + + final OIndexCursor cursor = index.iterateEntriesMajor(getIndexKey(index.getDefinition(), value, context), false, ascOrder); + fetchEntriesFromIndexCursor(cursor); + } else if (indexOperator instanceof OQueryOperatorMajorEquals) { + final Object value = compiledFilter.getRootCondition().getRight(); + final OIndexCursor cursor = index.iterateEntriesMajor(getIndexKey(index.getDefinition(), value, context), true, ascOrder); + fetchEntriesFromIndexCursor(cursor); + + } else if (indexOperator instanceof OQueryOperatorMinor) { + final Object value = compiledFilter.getRootCondition().getRight(); + + OIndexCursor cursor = index.iterateEntriesMinor(getIndexKey(index.getDefinition(), value, context), false, ascOrder); + fetchEntriesFromIndexCursor(cursor); + } else if (indexOperator instanceof OQueryOperatorMinorEquals) { + final Object value = compiledFilter.getRootCondition().getRight(); + + OIndexCursor cursor = index.iterateEntriesMinor(getIndexKey(index.getDefinition(), value, context), true, ascOrder); + fetchEntriesFromIndexCursor(cursor); + } else if (indexOperator instanceof OQueryOperatorIn) { + final List origValues = (List) compiledFilter.getRootCondition().getRight(); + final List values = new ArrayList(origValues.size()); + for (Object val : origValues) { + if (index.getDefinition() instanceof OCompositeIndexDefinition) { + throw new OCommandExecutionException("Operator IN not supported yet."); + } + + val = getIndexKey(index.getDefinition(), val, context); + values.add(val); + } + + OIndexCursor cursor = index.iterateEntries(values, true); + fetchEntriesFromIndexCursor(cursor); + } else { + final Object right = compiledFilter.getRootCondition().getRight(); + Object keyValue = getIndexKey(index.getDefinition(), right, context); + if (keyValue == null) { + return; + } + + final Object res; + if (index.getDefinition().getParamCount() == 1) { + // CONVERT BEFORE SEARCH IF NEEDED + final OType type = index.getDefinition().getTypes()[0]; + keyValue = OType.convert(keyValue, type.getDefaultJavaType()); + + res = index.get(keyValue); + } else { + final Object secondKey = getIndexKey(index.getDefinition(), right, context); + if (keyValue instanceof OCompositeKey && secondKey instanceof OCompositeKey + && ((OCompositeKey) keyValue).getKeys().size() == index.getDefinition().getParamCount() + && ((OCompositeKey) secondKey).getKeys().size() == index.getDefinition().getParamCount()) { + res = index.get(keyValue); + } else { + OIndexCursor cursor = index.iterateEntriesBetween(keyValue, true, secondKey, true, true); + fetchEntriesFromIndexCursor(cursor); + return; + } + + } + + if (res != null) { + if (res instanceof Collection) { + // MULTI VALUES INDEX + for (final OIdentifiable r : (Collection) res) { + final ODocument record = createIndexEntryAsDocument(keyValue, r.getIdentity()); + applyGroupBy(record, context); + if (!handleResult(record, context)) + // LIMIT REACHED + { + break; + } + } + } else { + // SINGLE VALUE INDEX + final ODocument record = createIndexEntryAsDocument(keyValue, ((OIdentifiable) res).getIdentity()); + applyGroupBy(record, context); + handleResult(record, context); + } + } + } + + } else { + if (isIndexSizeQuery()) { + getProjectionGroup(null, context).applyValue(projections.keySet().iterator().next(), index.getSize()); + return; + } + + if (isIndexKeySizeQuery()) { + getProjectionGroup(null, context).applyValue(projections.keySet().iterator().next(), index.getKeySize()); + return; + } + + final OIndexInternal indexInternal = index.getInternal(); + if (indexInternal instanceof OSharedResource) { + ((OSharedResource) indexInternal).acquireExclusiveLock(); + } + + try { + + // ADD ALL THE ITEMS AS RESULT + if (ascOrder) { + final OIndexCursor cursor = index.cursor(); + fetchEntriesFromIndexCursor(cursor); + } else { + + final OIndexCursor cursor = index.descCursor(); + fetchEntriesFromIndexCursor(cursor); + } + } finally { + if (indexInternal instanceof OSharedResource) { + ((OSharedResource) indexInternal).releaseExclusiveLock(); + } + } + } + } + + private boolean isIndexSizeQuery() { + if (!(aggregate && projections.entrySet().size() == 1)) { + return false; + } + + final Object projection = projections.values().iterator().next(); + if (!(projection instanceof OSQLFunctionRuntime)) { + return false; + } + + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) projection; + return f.getRoot().equals(OSQLFunctionCount.NAME) && ((f.configuredParameters == null || f.configuredParameters.length == 0) + || (f.configuredParameters.length == 1 && f.configuredParameters[0].equals("*"))); + } + + private boolean isIndexKeySizeQuery() { + if (!(aggregate && projections.entrySet().size() == 1)) { + return false; + } + + final Object projection = projections.values().iterator().next(); + if (!(projection instanceof OSQLFunctionRuntime)) { + return false; + } + + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) projection; + if (!f.getRoot().equals(OSQLFunctionCount.NAME)) { + return false; + } + + if (!(f.configuredParameters != null && f.configuredParameters.length == 1 + && f.configuredParameters[0] instanceof OSQLFunctionRuntime)) { + return false; + } + + final OSQLFunctionRuntime fConfigured = (OSQLFunctionRuntime) f.configuredParameters[0]; + if (!fConfigured.getRoot().equals(OSQLFunctionDistinct.NAME)) { + return false; + } + + if (!(fConfigured.configuredParameters != null && fConfigured.configuredParameters.length == 1 + && fConfigured.configuredParameters[0] instanceof OSQLFilterItemField)) { + return false; + } + + final OSQLFilterItemField field = (OSQLFilterItemField) fConfigured.configuredParameters[0]; + return field.getRoot().equals("key"); + } + + private void handleNoTarget() { + if (parsedTarget == null && expandTarget == null) + // ONLY LET, APPLY TO THEM + addResult(ORuntimeResult.createProjectionDocument(getTemporaryRIDCounter(context)), context); + } + + private void handleGroupBy(final OCommandContext iContext) { + if (aggregate && tempResult == null) { + + final long startGroupBy = System.currentTimeMillis(); + try { + + tempResult = new ArrayList(); + + for (Entry g : groupedResult.entrySet()) { + if (g.getKey() != null || (groupedResult.size() == 1 && groupByFields == null)) { + final ODocument doc = g.getValue().getResult(); + if (doc != null) { + ((List) tempResult).add(doc); + } + } + } + + } finally { + iContext.setVariable("groupByElapsed", (System.currentTimeMillis() - startGroupBy)); + } + } + } + + public void setProjections(final Map projections) { + this.projections = projections; + } + + public Map getProjectionDefinition() { + return projectionDefinition; + } + + public void setProjectionDefinition(final Map projectionDefinition) { + this.projectionDefinition = projectionDefinition; + } + + public void setOrderedFields(final List> orderedFields) { + this.orderedFields = orderedFields; + } + + public void setGroupByFields(final List groupByFields) { + this.groupByFields = groupByFields; + } + + public void setFetchLimit(final int fetchLimit) { + this.fetchLimit = fetchLimit; + } + + public void setFetchPlan(final String fetchPlan) { + this.fetchPlan = fetchPlan; + } + + public void setParallel(final boolean parallel) { + this.parallel = parallel; + } + + public void setNoCache(final boolean noCache) { + this.noCache = noCache; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.READ; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSetAware.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSetAware.java new file mode 100644 index 00000000000..17256721755 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLSetAware.java @@ -0,0 +1,269 @@ +/* + * + * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com) + * + * Licensed 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 com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.tx.OTransaction; + +import java.util.*; + +/** + * @author luca.molino + * + */ +public abstract class OCommandExecutorSQLSetAware extends OCommandExecutorSQLAbstract { + + protected static final String KEYWORD_SET = "SET"; + protected static final String KEYWORD_CONTENT = "CONTENT"; + + protected ODocument content = null; + protected int parameterCounter = 0; + + protected void parseContent() { + if (!parserIsEnded() && !parserGetLastWord().equals(KEYWORD_WHERE)) + content = parseJSON(); + + if (content == null) + throwSyntaxErrorException("Content not provided. Example: CONTENT { \"name\": \"Jay\" }"); + } + + protected void parseSetFields(final OClass iClass, final List> fields) { + String fieldName; + String fieldValue; + + boolean firstLap = true; + while (!parserIsEnded() && (firstLap || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',')) { + fieldName = parserRequiredWord(false, "Field name expected"); + if (fieldName.equalsIgnoreCase(KEYWORD_WHERE)) { + parserGoBack(); + break; + } + + parserNextChars(false, true, "="); + fieldValue = parserRequiredWord(false, "Value expected", " =><,\r\n", true); + + // INSERT TRANSFORMED FIELD VALUE + Object v = convertValue(iClass, fieldName, getFieldValueCountingParameters(fieldValue)); + v = reattachInTx(v); + fields.add(new OPair(fieldName, v)); + parserSkipWhiteSpaces(); + firstLap = false; + } + + if (fields.size() == 0) + throwParsingException("Entries to set = are missed. Example: name = 'Bill', salary = 300.2"); + } + + protected Object reattachInTx(Object fVal) { + if (fVal == null) { + return null; + } + OTransaction tx = getDatabase().getTransaction(); + if (!tx.isActive()) { + return fVal; + } + if (fVal instanceof ORID && ((ORID) fVal).isTemporary()) { + ORecord txVal = tx.getRecord((ORID) fVal); + if (txVal != null) { + return txVal; + } + } else if (!(fVal instanceof OIdentifiable) && OMultiValue.isMultiValue(fVal)) { + Iterator iter = OMultiValue.getMultiValueIterator(fVal); + if (fVal instanceof List) { + List result = new ArrayList(); + while (iter.hasNext()) { + result.add(reattachInTx(iter.next())); + } + return result; + } else if (fVal instanceof Set) { + Set result = new HashSet(); + while (iter.hasNext()) { + result.add(reattachInTx(iter.next())); + } + return result; + } + } + return fVal; + } + + + protected OClass extractClassFromTarget(String iTarget) { + // CLASS + if (!iTarget.toUpperCase(Locale.ENGLISH).startsWith(OCommandExecutorSQLAbstract.CLUSTER_PREFIX) + && !iTarget.startsWith(OCommandExecutorSQLAbstract.INDEX_PREFIX)) { + + if (iTarget.toUpperCase(Locale.ENGLISH).startsWith(OCommandExecutorSQLAbstract.CLASS_PREFIX)) + // REMOVE CLASS PREFIX + iTarget = iTarget.substring(OCommandExecutorSQLAbstract.CLASS_PREFIX.length()); + + if (iTarget.charAt(0) == ORID.PREFIX) + return getDatabase().getMetadata().getSchema().getClassByClusterId(new ORecordId(iTarget).getClusterId()); + + return getDatabase().getMetadata().getSchema().getClass(iTarget); + } + //CLUSTER + if (iTarget.toUpperCase(Locale.ENGLISH).startsWith(OCommandExecutorSQLAbstract.CLUSTER_PREFIX)) { + String clusterName = iTarget.substring(OCommandExecutorSQLAbstract.CLUSTER_PREFIX.length()).trim(); + ODatabaseDocumentInternal db = getDatabase(); + if(clusterName.startsWith("[") && clusterName.endsWith("]")) { + String[] clusterNames = clusterName.substring(1, clusterName.length()-1).split(","); + OClass candidateClass = null; + for(String cName:clusterNames){ + OCluster aCluster = db.getStorage().getClusterByName(cName.trim()); + if(aCluster == null){ + return null; + } + OClass aClass = db.getMetadata().getSchema().getClassByClusterId(aCluster.getId()); + if(aClass == null){ + return null; + } + if(candidateClass == null || candidateClass.equals(aClass) || candidateClass.isSubClassOf(aClass)){ + candidateClass = aClass; + }else if(!candidateClass.isSuperClassOf(aClass)){ + return null; + } + } + return candidateClass; + } else { + OCluster cluster = db.getStorage().getClusterByName(clusterName); + if (cluster != null) { + return db.getMetadata().getSchema().getClassByClusterId(cluster.getId()); + } + } + } + return null; + } + + protected Object convertValue(OClass iClass, String fieldName, Object v) { + if (iClass != null) { + // CHECK TYPE AND CONVERT IF NEEDED + final OProperty p = iClass.getProperty(fieldName); + if (p != null) { + final OClass embeddedType = p.getLinkedClass(); + + switch (p.getType()) { + case EMBEDDED: + // CONVERT MAP IN DOCUMENTS ASSIGNING THE CLASS TAKEN FROM SCHEMA + if (v instanceof Map) + v = createDocumentFromMap(embeddedType, (Map) v); + break; + + case EMBEDDEDSET: + // CONVERT MAPS IN DOCUMENTS ASSIGNING THE CLASS TAKEN FROM SCHEMA + if (v instanceof Map) + return createDocumentFromMap(embeddedType, (Map) v); + else if (OMultiValue.isMultiValue(v)) { + final Set set = new HashSet(); + + for (Object o : OMultiValue.getMultiValueIterable(v)) { + if (o instanceof Map) { + final ODocument doc = createDocumentFromMap(embeddedType, (Map) o); + set.add(doc); + } else if (o instanceof OIdentifiable) + set.add(((OIdentifiable) o).getRecord()); + else + set.add(o); + } + + v = set; + } + break; + + case EMBEDDEDLIST: + // CONVERT MAPS IN DOCUMENTS ASSIGNING THE CLASS TAKEN FROM SCHEMA + if (v instanceof Map) + return createDocumentFromMap(embeddedType, (Map) v); + else if (OMultiValue.isMultiValue(v)) { + final List set = new ArrayList(); + + for (Object o : OMultiValue.getMultiValueIterable(v)) { + if (o instanceof Map) { + final ODocument doc = createDocumentFromMap(embeddedType, (Map) o); + set.add(doc); + } else if (o instanceof OIdentifiable) + set.add(((OIdentifiable) o).getRecord()); + else + set.add(o); + } + + v = set; + } + break; + + case EMBEDDEDMAP: + // CONVERT MAPS IN DOCUMENTS ASSIGNING THE CLASS TAKEN FROM SCHEMA + if (v instanceof Map) { + final Map map = new HashMap(); + + for (Map.Entry entry : ((Map) v).entrySet()) { + if (entry.getValue() instanceof Map) { + final ODocument doc = createDocumentFromMap(embeddedType, (Map) entry.getValue()); + map.put(entry.getKey(), doc); + } else if (entry.getValue() instanceof OIdentifiable) + map.put(entry.getKey(), ((OIdentifiable) entry.getValue()).getRecord()); + else + map.put(entry.getKey(), entry.getValue()); + } + + v = map; + } + break; + } + } + } + return v; + } + + private ODocument createDocumentFromMap(OClass embeddedType, Map o) { + final ODocument doc = new ODocument(); + if (embeddedType != null) + doc.setClassName(embeddedType.getName()); + + doc.fromMap(o); + return doc; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + protected Object getFieldValueCountingParameters(String fieldValue) { + if (fieldValue.trim().equals("?")) + parameterCounter++; + return OSQLHelper.parseValue(this, fieldValue, context, true); + } + + protected ODocument parseJSON() { + final String contentAsString = parserRequiredWord(false, "JSON expected").trim(); + final ODocument json = new ODocument().fromJSON(contentAsString); + parserSkipWhiteSpaces(); + return json; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTransactional.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTransactional.java new file mode 100755 index 00000000000..ca1893ff55f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTransactional.java @@ -0,0 +1,66 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import java.util.Map; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +/** + * Acts as a delegate to the real command inserting the execution of the command inside a new transaction if not yet begun. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLTransactional extends OCommandExecutorSQLDelegate { + public static final String KEYWORD_TRANSACTIONAL = "TRANSACTIONAL"; + + @SuppressWarnings("unchecked") + @Override + public OCommandExecutorSQLTransactional parse(OCommandRequest iCommand) { + String cmd = ((OCommandSQL) iCommand).getText(); + super.parse(new OCommandSQL(cmd.substring(KEYWORD_TRANSACTIONAL.length()))); + return this; + } + + @Override + public Object execute(Map iArgs) { + final ODatabaseDocument database = getDatabase(); + boolean txbegun = database.getTransaction() == null || !database.getTransaction().isActive(); + + if (txbegun) + database.begin(); + + try { + final Object result = super.execute(iArgs); + + if (txbegun) + database.commit(); + + return result; + } catch (Exception e) { + if (txbegun) + database.rollback(); + throw OException.wrapException(new OCommandExecutionException("Transactional command failed"), e); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTraverse.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTraverse.java new file mode 100755 index 00000000000..fd9ab053d76 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTraverse.java @@ -0,0 +1,270 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.command.traverse.OTraverse; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.*; + +/** + * Executes a TRAVERSE crossing records. Returns a List containing all the traversed records that match the WHERE + * condition. + *

          + * SYNTAX: TRAVERSE * FROM WHERE + *

          + *

          + * In the command context you've access to the variable $depth containing the depth level from the root node. This is useful to + * limit the traverse up to a level. For example to consider from the first depth level (0 is root node) to the third use: + * TRAVERSE children FROM #5:23 WHERE $depth BETWEEN 1 AND 3. To filter traversed records use it combined with a SELECT + * statement: + *

          + *

          + * SELECT FROM (TRAVERSE children FROM #5:23 WHERE $depth BETWEEN 1 AND 3) WHERE city.name = 'Rome' + *

          + * + * @author Luca Garulli + */ +@SuppressWarnings("unchecked") +public class OCommandExecutorSQLTraverse extends OCommandExecutorSQLResultsetAbstract { + public static final String KEYWORD_WHILE = "WHILE"; + public static final String KEYWORD_TRAVERSE = "TRAVERSE"; + public static final String KEYWORD_STRATEGY = "STRATEGY"; + public static final String KEYWORD_MAXDEPTH = "MAXDEPTH"; + + // HANDLES ITERATION IN LAZY WAY + private OTraverse traverse = new OTraverse(); + + /** + * Compile the filter conditions only the first time. + */ + public OCommandExecutorSQLTraverse parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + // System.out.println("NEW PARSER FROM: " + queryText); + queryText = preParse(queryText, iRequest); + // System.out.println("NEW PARSER TO: " + queryText); + textRequest.setText(queryText); + + super.parse(iRequest); + + final int pos = parseFields(); + if (pos == -1) + throw new OCommandSQLParsingException("Traverse must have the field list. Use " + getSyntax()); + parserSetCurrentPosition(pos); + + int endPosition = parserText.length(); + + parsedTarget = OSQLEngine.getInstance().parseTarget(parserText.substring(pos, endPosition), getContext()); + + if (parsedTarget.parserIsEnded()) + parserSetCurrentPosition(endPosition); + else + parserMoveCurrentPosition(parsedTarget.parserGetCurrentPosition()); + + if (!parserIsEnded()) { + parserNextWord(true); + + if (parserGetLastWord().equalsIgnoreCase(KEYWORD_WHERE)) + // // TODO Remove the additional management of WHERE for TRAVERSE after a while + warnDeprecatedWhere(); + + if (parserGetLastWord().equalsIgnoreCase(KEYWORD_WHERE) || parserGetLastWord().equalsIgnoreCase(KEYWORD_WHILE)) { + + compiledFilter = OSQLEngine.getInstance().parseCondition(parserText.substring(parserGetCurrentPosition(), endPosition), + getContext(), KEYWORD_WHILE); + + traverse.predicate(compiledFilter); + optimize(); + parserSetCurrentPosition(compiledFilter.parserIsEnded() ? endPosition + : compiledFilter.parserGetCurrentPosition() + parserGetCurrentPosition()); + } else + parserGoBack(); + } + + parserSkipWhiteSpaces(); + + while (!parserIsEnded()) { + if (parserOptionalKeyword(KEYWORD_LIMIT, KEYWORD_SKIP, KEYWORD_OFFSET, KEYWORD_TIMEOUT, KEYWORD_MAXDEPTH, + KEYWORD_STRATEGY)) { + final String w = parserGetLastWord(); + if (w.equals(KEYWORD_LIMIT)) + parseLimit(w); + else if (w.equals(KEYWORD_SKIP) || w.equals(KEYWORD_OFFSET)) + parseSkip(w); + else if (w.equals(KEYWORD_TIMEOUT)) + parseTimeout(w); + else if (w.equals(KEYWORD_MAXDEPTH)) + parseMaxDepth(w); + else if (w.equals(KEYWORD_STRATEGY)) + parseStrategy(w); + } + } + + if (limit == 0 || limit < -1) + throw new IllegalArgumentException("Limit must be > 0 or = -1 (no limit)"); + else + traverse.limit(limit); + + traverse.getContext().setParent(iRequest.getContext()); + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + protected boolean parseMaxDepth(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_MAXDEPTH)) + return false; + + String word = parserNextWord(true); + + try { + traverse.setMaxDepth(Integer.parseInt(word)); + } catch (Exception e) { + throwParsingException("Invalid " + KEYWORD_MAXDEPTH + " value set to '" + word + "' but it should be a valid long. Example: " + + KEYWORD_MAXDEPTH + " 3000"); + } + + if (traverse.getMaxDepth() < 0) + throwParsingException("Invalid " + KEYWORD_MAXDEPTH + ": value set minor than ZERO. Example: " + KEYWORD_MAXDEPTH + " 3"); + + return true; + } + + public Object execute(final Map iArgs) { + context.beginExecution(timeoutMs, timeoutStrategy); + + if (!assignTarget(iArgs)) + throw new OQueryParsingException("No source found in query: specify class, cluster(s) or single record(s)"); + + try { + // BROWSE ALL THE RECORDS AND COLLECTS RESULT + final List result = traverse.execute(); + for (OIdentifiable r : result) + if (!handleResult(r, context)) + // LIMIT REACHED + break; + + return getResult(); + } finally { + request.getResultListener().end(); + } + } + + @Override + public OCommandContext getContext() { + return traverse.getContext(); + } + + public Iterator iterator() { + return iterator(null); + } + + public Iterator iterator(final Map iArgs) { + assignTarget(iArgs); + return traverse; + } + + public String getSyntax() { + return "TRAVERSE * FROM [MAXDEPTH ] [WHILE ] [STRATEGY ]"; + } + + protected void warnDeprecatedWhere() { + OLogManager.instance().warn(this, + "Keyword WHERE in traverse has been replaced by WHILE. Please change your query to support WHILE instead of WHERE because now it's only deprecated, but in future it will be removed the back-ward compatibility."); + } + + @Override + protected boolean assignTarget(Map iArgs) { + if (super.assignTarget(iArgs)) { + traverse.target(target); + return true; + } + return false; + } + + protected int parseFields() { + int currentPos = 0; + final StringBuilder word = new StringBuilder(); + + currentPos = nextWord(parserText, parserTextUpperCase, currentPos, word, true); + if (!word.toString().equals(KEYWORD_TRAVERSE)) + return -1; + + int fromPosition = parserTextUpperCase.indexOf(KEYWORD_FROM_2FIND, currentPos); + if (fromPosition == -1) + throw new OQueryParsingException("Missed " + KEYWORD_FROM, parserText, currentPos); + + Set fields = new HashSet(); + + final String fieldString = parserText.substring(currentPos, fromPosition).trim(); + if (fieldString.length() > 0) { + // EXTRACT PROJECTIONS + final List items = OStringSerializerHelper.smartSplit(fieldString, ','); + + for (String field : items) { + final String fieldName = field.trim(); + + if (fieldName.contains("(")) + fields.add(OSQLHelper.parseValue(null, fieldName, context)); + else + fields.add(fieldName); + } + } else + throw new OQueryParsingException("Missed field list to cross in TRAVERSE. Use " + getSyntax(), parserText, currentPos); + + currentPos = fromPosition + KEYWORD_FROM.length() + 1; + + traverse.fields(fields); + + return currentPos; + } + + /** + * Parses the strategy keyword if found. + */ + protected boolean parseStrategy(final String w) throws OCommandSQLParsingException { + if (!w.equals(KEYWORD_STRATEGY)) + return false; + + final String strategyWord = parserNextWord(true); + + try { + traverse.setStrategy(OTraverse.STRATEGY.valueOf(strategyWord.toUpperCase(Locale.ENGLISH))); + } catch (IllegalArgumentException e) { + throwParsingException("Invalid " + KEYWORD_STRATEGY + ". Use one between " + Arrays.toString(OTraverse.STRATEGY.values())); + } + return true; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.READ; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateClass.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateClass.java new file mode 100755 index 00000000000..b5b4ab9036f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateClass.java @@ -0,0 +1,187 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.cache.OCommandCache; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.io.IOException; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; + +/** + * SQL TRUNCATE CLASS command: Truncates an entire class deleting all configured clusters where the class relies on. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLTruncateClass extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_TRUNCATE = "TRUNCATE"; + public static final String KEYWORD_CLASS = "CLASS"; + public static final String KEYWORD_POLYMORPHIC = "POLYMORPHIC"; + private OClass schemaClass; + private boolean unsafe = false; + private boolean deep = false; + + @SuppressWarnings("unchecked") public OCommandExecutorSQLTruncateClass parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_TRUNCATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_TRUNCATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLASS)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLASS + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserText, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Expected class name. Use " + getSyntax(), parserText, oldPos); + + final String className = word.toString(); + schemaClass = database.getMetadata().getSchema().getClass(className); + + if (schemaClass == null) + throw new OCommandSQLParsingException("Class '" + className + "' not found", parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserText, oldPos, word, true); + + while (pos > 0) { + String nextWord = word.toString(); + if (nextWord.toUpperCase(Locale.ENGLISH).equals(KEYWORD_UNSAFE)) { + unsafe = true; + } else if (nextWord.toUpperCase(Locale.ENGLISH).equals(KEYWORD_POLYMORPHIC)) { + deep = true; + } + oldPos = pos; + pos = nextWord(parserText, parserText, oldPos, word, true); + } + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + /** + * Execute the command. + */ + public Object execute(final Map iArgs) { + if (schemaClass == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final long recs = schemaClass.count(deep); + if (recs > 0 && !unsafe) { + if (schemaClass.isSubClassOf("V")) { + throw new OCommandExecutionException( + "'TRUNCATE CLASS' command cannot be used on not empty vertex classes. Apply the 'UNSAFE' keyword to force it (at your own risk)"); + } else if (schemaClass.isSubClassOf("E")) { + throw new OCommandExecutionException( + "'TRUNCATE CLASS' command cannot be used on not empty edge classes. Apply the 'UNSAFE' keyword to force it (at your own risk)"); + } + } + + Collection subclasses = schemaClass.getAllSubclasses(); + if (deep && !unsafe) {// for multiple inheritance + for (OClass subclass : subclasses) { + long subclassRecs = schemaClass.count(); + if (subclassRecs > 0) { + if (subclass.isSubClassOf("V")) { + throw new OCommandExecutionException( + "'TRUNCATE CLASS' command cannot be used on not empty vertex classes (" + subclass.getName() + + "). Apply the 'UNSAFE' keyword to force it (at your own risk)"); + } else if (subclass.isSubClassOf("E")) { + throw new OCommandExecutionException( + "'TRUNCATE CLASS' command cannot be used on not empty edge classes (" + subclass.getName() + + "). Apply the 'UNSAFE' keyword to force it (at your own risk)"); + } + } + } + } + + try { + schemaClass.truncate(); + invalidateCommandCache(schemaClass); + if (deep) { + for (OClass subclass : subclasses) { + subclass.truncate(); + invalidateCommandCache(subclass); + } + } + } catch (IOException e) { + throw OException.wrapException(new OCommandExecutionException("Error on executing command"), e); + } + + return recs; + } + + private void invalidateCommandCache(OClass clazz) { + if (clazz == null) { + return; + } + OCommandCache commandCache = getDatabase().getMetadata().getCommandCache(); + if (commandCache != null && commandCache.isEnabled()) { + int[] clusterIds = clazz.getClusterIds(); + if (clusterIds != null) { + for (int i : clusterIds) { + String clusterName = getDatabase().getClusterNameById(i); + if (clusterName != null) { + commandCache.invalidateResultsOfCluster(clusterName); + } + } + } + } + } + + @Override public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override public String getSyntax() { + return "TRUNCATE CLASS [UNSAFE] [POLYMORPHIC]"; + } + + @Override public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateCluster.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateCluster.java new file mode 100755 index 00000000000..7a14da42973 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateCluster.java @@ -0,0 +1,152 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.sql.parser.OIdentifier; +import com.orientechnologies.orient.core.sql.parser.OTruncateClusterStatement; +import com.orientechnologies.orient.core.storage.OCluster; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.io.IOException; +import java.util.Map; + +/** + * SQL TRUNCATE CLUSTER command: Truncates an entire record cluster. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLTruncateCluster extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_TRUNCATE = "TRUNCATE"; + public static final String KEYWORD_CLUSTER = "CLUSTER"; + private String clusterName; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLTruncateCluster parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_TRUNCATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_TRUNCATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_CLUSTER)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLUSTER + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserText, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Expected cluster name. Use " + getSyntax(), parserText, oldPos); + + clusterName = decodeClusterName(word.toString()); + + if (preParsedStatement != null) { // new parser, this will be removed and implemented with the new executor + OIdentifier name = ((OTruncateClusterStatement) preParsedStatement).clusterName; + if (name != null) { + clusterName = name.getStringValue(); + } + } + + final ODatabaseDocument database = getDatabase(); + if (database.getClusterIdByName(clusterName) == -1) + throw new OCommandSQLParsingException("Cluster '" + clusterName + "' not found", parserText, oldPos); + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + private String decodeClusterName(String s) { + return decodeClassName(s); + } + + /** + * Execute the command. + */ + public Object execute(final Map iArgs) { + if (clusterName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + final ODatabaseDocumentInternal database = getDatabase(); + + final int clusterId = database.getClusterIdByName(clusterName); + if (clusterId < 0) { + throw new ODatabaseException("Cluster with name " + clusterName + " does not exist"); + } + + final OSchema schema = database.getMetadata().getSchema(); + final OClass clazz = schema.getClassByClusterId(clusterId); + if (clazz == null) { + final OStorage storage = database.getStorage(); + final OCluster cluster = storage.getClusterById(clusterId); + + if (cluster == null) { + throw new ODatabaseException("Cluster with name " + clusterName + " does not exist"); + } + + try { + storage.checkForClusterPermissions(cluster.getName()); + cluster.truncate(); + } catch (IOException ioe) { + throw OException.wrapException(new ODatabaseException("Error during truncation of cluster with name " + clusterName), ioe); + } + } else { + clazz.truncateCluster(clusterName); + } + return true; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public String getSyntax() { + return "TRUNCATE CLUSTER "; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateRecord.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateRecord.java new file mode 100755 index 00000000000..7733f025d96 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLTruncateRecord.java @@ -0,0 +1,134 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.storage.OStorageOperationResult; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * SQL TRUNCATE RECORD command: Truncates a record without loading it. Useful when the record is dirty in any way and cannot be + * loaded correctly. + * + * @author Luca Garulli + * + */ +public class OCommandExecutorSQLTruncateRecord extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest { + public static final String KEYWORD_TRUNCATE = "TRUNCATE"; + public static final String KEYWORD_RECORD = "RECORD"; + private Set records = new HashSet(); + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLTruncateRecord parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + textRequest.setText(queryText); + + init((OCommandRequestText) iRequest); + + StringBuilder word = new StringBuilder(); + + int oldPos = 0; + int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_TRUNCATE)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_TRUNCATE + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true); + if (pos == -1 || !word.toString().equals(KEYWORD_RECORD)) + throw new OCommandSQLParsingException("Keyword " + KEYWORD_RECORD + " not found. Use " + getSyntax(), parserText, oldPos); + + oldPos = pos; + pos = nextWord(parserText, parserText, oldPos, word, true); + if (pos == -1) + throw new OCommandSQLParsingException("Expected one or more records. Use " + getSyntax(), parserText, oldPos); + + if (word.charAt(0) == '[') + // COLLECTION + OStringSerializerHelper.getCollection(parserText, oldPos, records); + else { + records.add(word.toString()); + } + + if (records.isEmpty()) + throw new OCommandSQLParsingException("Missed record(s). Use " + getSyntax(), parserText, oldPos); + } finally { + textRequest.setText(originalQuery); + } + return this; + } + + /** + * Execute the command. + */ + public Object execute(final Map iArgs) { + if (records.isEmpty()) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + int deleted = 0; + + final ODatabaseDocumentInternal database = getDatabase(); + for (String rec : records) { + try { + final ORecordId rid = new ORecordId(rec); + final OStorageOperationResult result = database.getStorage().deleteRecord(rid, -1, 0, null); + database.getLocalCache().deleteRecord(rid); + + if (result.getResult()) + deleted++; + + } catch (Throwable e) { + throw OException.wrapException(new OCommandExecutionException("Error on executing command"), e); + } + } + + return deleted; + } + + @Override + public long getDistributedTimeout() { + return OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT.getValueAsLong(); + } + + @Override + public String getSyntax() { + return "TRUNCATE RECORD *"; + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLUpdate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLUpdate.java new file mode 100755 index 00000000000..47654f0f1af --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorSQLUpdate.java @@ -0,0 +1,968 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.common.util.OTriple; +import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.command.OCommandResultListener; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.*; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OConcurrentModificationException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.OSecurity; +import com.orientechnologies.orient.core.query.OQuery; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilter; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.parser.OUpdateStatement; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.storage.ORecordDuplicatedException; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.*; + +/** + * SQL UPDATE command. + * + * @author Luca Garulli + */ +public class OCommandExecutorSQLUpdate extends OCommandExecutorSQLRetryAbstract + implements OCommandDistributedReplicateRequest, OCommandResultListener { + public static final String KEYWORD_UPDATE = "UPDATE"; + private static final String KEYWORD_ADD = "ADD"; + private static final String KEYWORD_PUT = "PUT"; + private static final String KEYWORD_REMOVE = "REMOVE"; + private static final String KEYWORD_INCREMENT = "INCREMENT"; + private static final String KEYWORD_MERGE = "MERGE"; + private static final String KEYWORD_UPSERT = "UPSERT"; + private static final String KEYWORD_EDGE = "EDGE"; + private static final Object EMPTY_VALUE = new Object(); + private List> setEntries = new ArrayList>(); + private List> addEntries = new ArrayList>(); + private List> putEntries = new ArrayList>(); + private List> removeEntries = new ArrayList>(); + private List> incrementEntries = new ArrayList>(); + private ODocument merge = null; + private String lockStrategy = "NONE"; + private OReturnHandler returnHandler = new ORecordCountHandler(); + private OQuery query; + private OSQLFilter compiledFilter; + private String subjectName; + private OCommandParameters parameters; + private boolean upsertMode = false; + private boolean isUpsertAllowed = false; + private boolean updated = false; + private OClass clazz = null; + private DISTRIBUTED_EXECUTION_MODE distributedMode; + + private boolean updateEdge = false; + + @SuppressWarnings("unchecked") + public OCommandExecutorSQLUpdate parse(final OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + + String queryText = textRequest.getText(); + String originalQuery = queryText; + try { + queryText = preParse(queryText, iRequest); + if (isUpdateEdge()) { + queryText = queryText.replaceFirst("EDGE ", "");// work-around to use UPDATE syntax without having to + } + textRequest.setText(queryText); + + final ODatabaseDocument database = getDatabase(); + + init((OCommandRequestText) iRequest); + + setEntries.clear(); + addEntries.clear(); + putEntries.clear(); + removeEntries.clear(); + incrementEntries.clear(); + content = null; + merge = null; + + query = null; + + parserRequiredKeyword(KEYWORD_UPDATE); + + subjectName = parserRequiredWord(false, "Invalid target", " =><,\r\n"); + if (subjectName == null) { + throwSyntaxErrorException("Invalid subject name. Expected cluster, class, index or sub-query"); + } + if (subjectName.equalsIgnoreCase("EDGE")) { + updateEdge = true; + subjectName = parserRequiredWord(false, "Invalid target", " =><,\r\n"); + } + + clazz = extractClassFromTarget(subjectName); + + String word = parserNextWord(true); + + if (parserIsEnded() || (!word.equals(KEYWORD_SET) && !word.equals(KEYWORD_ADD) && !word.equals(KEYWORD_PUT) && !word + .equals(KEYWORD_REMOVE) && !word.equals(KEYWORD_INCREMENT) && !word.equals(KEYWORD_CONTENT) && !word.equals(KEYWORD_MERGE) + && !word.equals(KEYWORD_LOCK) && !word.equals(KEYWORD_RETURN) && !word.equals(KEYWORD_UPSERT) && !word + .equals(KEYWORD_EDGE))) + throwSyntaxErrorException( + "Expected keyword " + KEYWORD_SET + "," + KEYWORD_ADD + "," + KEYWORD_CONTENT + "," + KEYWORD_MERGE + "," + KEYWORD_PUT + + "," + KEYWORD_REMOVE + "," + KEYWORD_INCREMENT + "," + KEYWORD_LOCK + " or " + KEYWORD_RETURN + " or " + + KEYWORD_UPSERT + " or " + KEYWORD_EDGE); + + while ((!parserIsEnded() && !parserGetLastWord().equals(OCommandExecutorSQLAbstract.KEYWORD_WHERE)) || parserGetLastWord() + .equals(KEYWORD_UPSERT)) { + word = parserGetLastWord(); + + if (word.equals(KEYWORD_CONTENT)) + parseContent(); + else if (word.equals(KEYWORD_MERGE)) + parseMerge(); + else if (word.equals(KEYWORD_SET)) + parseSetFields(clazz, setEntries); + else if (word.equals(KEYWORD_ADD)) + parseAddFields(clazz); + else if (word.equals(KEYWORD_PUT)) + parsePutFields(); + else if (word.equals(KEYWORD_REMOVE)) + parseRemoveFields(); + else if (word.equals(KEYWORD_INCREMENT)) + parseIncrementFields(); + else if (word.equals(KEYWORD_LOCK)) + lockStrategy = parseLock(); + else if (word.equals(KEYWORD_UPSERT)) + upsertMode = true; + else if (word.equals(KEYWORD_RETURN)) + parseReturn(); + else if (word.equals(KEYWORD_RETRY)) { + OLogManager.instance().warn(this, "RETRY keyword will be ignored in " + originalQuery); + parseRetry(); + } else + break; + + parserNextWord(true); + } + + final String additionalStatement = parserGetLastWord(); + + if (subjectName.startsWith("(")) { + subjectName = subjectName.trim(); + query = database + .command(new OSQLAsynchQuery(subjectName.substring(1, subjectName.length() - 1), this).setContext(context)); + + if (additionalStatement.equals(OCommandExecutorSQLAbstract.KEYWORD_WHERE) || additionalStatement + .equals(OCommandExecutorSQLAbstract.KEYWORD_LIMIT)) + compiledFilter = OSQLEngine.getInstance() + .parseCondition(parserText.substring(parserGetCurrentPosition()), getContext(), KEYWORD_WHERE); + + } else if (additionalStatement.equals(OCommandExecutorSQLAbstract.KEYWORD_WHERE) || additionalStatement + .equals(OCommandExecutorSQLAbstract.KEYWORD_LIMIT) || additionalStatement.equals(OCommandExecutorSQLAbstract.KEYWORD_LET) + || additionalStatement.equals(KEYWORD_LOCK)) { + if (this.preParsedStatement != null) { + Map params = ((OCommandRequestText) iRequest).getParameters(); + OUpdateStatement updateStm = (OUpdateStatement) preParsedStatement; + StringBuilder selectString = new StringBuilder(); + selectString.append("select from "); + updateStm.target.toString(params, selectString); + if (updateStm.let != null) { + selectString.append(" "); + updateStm.let.toString(params, selectString); + } + if (updateStm.whereClause != null) { + selectString.append(" WHERE "); + updateStm.whereClause.toString(params, selectString); + } + if (updateStm.limit != null) { + selectString.append(" "); + updateStm.limit.toString(params, selectString); + } + if (updateStm.timeout != null) { + selectString.append(" "); + updateStm.timeout.toString(params, selectString); + } + if (updateStm.lockRecord != null) { + selectString.append(" LOCK "); + switch (updateStm.lockRecord) { + case DEFAULT: + selectString.append("DEFAULT"); + break; + case EXCLUSIVE_LOCK: + selectString.append("RECORD"); + break; + case SHARED_LOCK: + selectString.append("SHARED"); + break; + case NONE: + selectString.append("NONE"); + break; + } + } + + query = new OSQLAsynchQuery(selectString.toString(), this); + } else { + query = new OSQLAsynchQuery("select from " + getSelectTarget() + " " + additionalStatement + " " + parserText + .substring(parserGetCurrentPosition()), this); + } + + isUpsertAllowed = (((OMetadataInternal) getDatabase().getMetadata()).getImmutableSchemaSnapshot().getClass(subjectName) + != null); + } else if (!additionalStatement.isEmpty()) + throwSyntaxErrorException("Invalid keyword " + additionalStatement); + else + query = new OSQLAsynchQuery("select from " + getSelectTarget(), this); + + if (upsertMode && !isUpsertAllowed) + throwSyntaxErrorException("Upsert only works with class names "); + + if (upsertMode && !additionalStatement.equals(OCommandExecutorSQLAbstract.KEYWORD_WHERE)) + throwSyntaxErrorException("Upsert only works with WHERE keyword"); + if (upsertMode && updateEdge) + throwSyntaxErrorException("Upsert is not supported with UPDATE EDGE"); + } finally { + textRequest.setText(originalQuery); + } + + return this; + } + + private boolean isUpdateEdge() { + return updateEdge; + } + + private String getSelectTarget() { + if (preParsedStatement == null) { + return subjectName; + } + return ((OUpdateStatement) preParsedStatement).target.toString(); + } + + public Object execute(final Map iArgs) { + if (subjectName == null) + throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet"); + + parameters = new OCommandParameters(iArgs); + Map queryArgs; + if (parameters.size() > 0 && parameters.getByName(0) != null) { + queryArgs = new HashMap(); + for (int i = parameterCounter; i < parameters.size(); i++) { + if (parameters.getByName(i) != null) + queryArgs.put(i - parameterCounter, parameters.getByName(i)); + } + } else { + queryArgs = iArgs; + } + + query.setContext(context); + + returnHandler.reset(); + + if (lockStrategy.equals("RECORD")) + query.getContext().setVariable("$locking", OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK); + + getDatabase().query(query, queryArgs); + + if (upsertMode && !updated) { + // IF UPDATE DOES NOT PRODUCE RESULTS AND UPSERT MODE IS ENABLED, CREATE DOCUMENT AND APPLY SET/ADD/PUT/MERGE and so on + final ODocument doc = subjectName != null ? new ODocument(subjectName) : new ODocument(); + final String suspendedLockStrategy = lockStrategy; + lockStrategy = "NONE";// New record hasn't been created under exclusive lock - just to avoid releasing locks by result(doc) + try { + result(doc); + } catch (ORecordDuplicatedException e) { + if (upsertMode) + // UPDATE THE NEW RECORD + getDatabase().query(query, queryArgs); + else + throw e; + } catch (ORecordNotFoundException e) { + if (upsertMode) + // UPDATE THE NEW RECORD + getDatabase().query(query, queryArgs); + else + throw e; + } catch (OConcurrentModificationException e) { + if (upsertMode) + // UPDATE THE NEW RECORD + getDatabase().query(query, queryArgs); + else + throw e; + } + + lockStrategy = suspendedLockStrategy; + } + + return returnHandler.ret(); + } + + /** + * Update current record. + */ + @SuppressWarnings("unchecked") + public boolean result(final Object iRecord) { + final ODocument record = ((OIdentifiable) iRecord).getRecord(); + + if (isUpdateEdge() && !isRecordInstanceOf(iRecord, "E")) { + throw new OCommandExecutionException("Using UPDATE EDGE on a record that is not an instance of E"); + } + if (compiledFilter != null) { + // ADDITIONAL FILTERING + if (!(Boolean) compiledFilter.evaluate(record, null, context)) + return false; + } + + parameters.reset(); + + returnHandler.beforeUpdate(record); + + boolean updated = handleContent(record); + updated |= handleMerge(record); + updated |= handleSetEntries(record); + updated |= handleIncrementEntries(record); + updated |= handleAddEntries(record); + updated |= handlePutEntries(record); + updated |= handleRemoveEntries(record); + + if (updated) { + handleUpdateEdge(record); + record.setDirty(); + record.save(); + returnHandler.afterUpdate(record); + this.updated = true; + } + + return true; + } + + /** + * checks if an object is an OIdentifiable and an instance of a particular (schema) class + * + * @param iRecord The record object + * @param orientClass The schema class + * + * @return + */ + private boolean isRecordInstanceOf(Object iRecord, String orientClass) { + if (iRecord == null) { + return false; + } + if (!(iRecord instanceof OIdentifiable)) { + return false; + } + ODocument record = ((OIdentifiable) iRecord).getRecord(); + if (iRecord == null) { + return false; + } + return (record.getSchemaClass().isSubClassOf(orientClass)); + } + + /** + * handles vertex consistency after an UPDATE EDGE + * + * @param record the edge record + */ + private void handleUpdateEdge(ODocument record) { + if (!updateEdge) { + return; + } + Object currentOut = record.field("out"); + Object currentIn = record.field("in"); + + Object prevOut = record.getOriginalValue("out"); + Object prevIn = record.getOriginalValue("in"); + + validateOutInForEdge(record, currentOut, currentIn); + + changeVertexEdgePointer(record, (OIdentifiable) prevIn, (OIdentifiable) currentIn, "in"); + changeVertexEdgePointer(record, (OIdentifiable) prevOut, (OIdentifiable) currentOut, "out"); + } + + /** + * updates old and new vertices connected to an edge after out/in update on the edge itself + * + * @param edge the edge + * @param prevVertex the previously connected vertex + * @param currentVertex the currently connected vertex + * @param direction the direction ("out" or "in") + */ + private void changeVertexEdgePointer(ODocument edge, OIdentifiable prevVertex, OIdentifiable currentVertex, String direction) { + if (prevVertex != null && !prevVertex.equals(currentVertex)) { + String edgeClassName = edge.getClassName(); + if (edgeClassName.equalsIgnoreCase("E")) { + edgeClassName = ""; + } + String vertexFieldName = direction + "_" + edgeClassName; + ODocument prevOutDoc = ((OIdentifiable) prevVertex).getRecord(); + ORecordLazyMultiValue prevBag = prevOutDoc.field(vertexFieldName); + if (prevBag == null && edgeClassName.equalsIgnoreCase("E")) { + prevBag = prevOutDoc.field(vertexFieldName + "E"); + } + if (prevBag != null) { + if (prevBag instanceof ORidBag) { + ((ORidBag) prevBag).remove(edge); + } else if (prevBag instanceof List) { + ((List) prevBag).remove(edge); + } else if (prevBag instanceof Set) { + ((Set) prevBag).remove(edge); + } else { + throw new UnsupportedOperationException(); + } + prevOutDoc.save(); + } + + ODocument currentVertexDoc = ((OIdentifiable) currentVertex).getRecord(); + ORecordLazyMultiValue currentBag = currentVertexDoc.field(vertexFieldName); + if (currentBag == null) { + currentBag = new ORidBag(); + currentVertexDoc.field(vertexFieldName, currentBag); + } + if (currentBag instanceof ORidBag) { + ((ORidBag) currentBag).add(edge); + } else if (currentBag instanceof List) { + ((List) currentBag).add(edge); + } else if (currentBag instanceof Set) { + ((Set) currentBag).add(edge); + } else { + throw new UnsupportedOperationException(); + } + + } + } + + private void validateOutInForEdge(ODocument record, Object currentOut, Object currentIn) { + if (!isRecordInstanceOf(currentOut, "V")) { + throw new OCommandExecutionException("Error updating edge: 'out' is not a vertex - " + currentOut + ""); + } + if (!isRecordInstanceOf(currentIn, "V")) { + throw new OCommandExecutionException("Error updating edge: 'in' is not a vertex - " + currentIn + ""); + } + } + + @Override + public String getSyntax() { + return "UPDATE |cluster:> [SET|ADD|PUT|REMOVE|INCREMENT|CONTENT {}|MERGE {}] [[,] = |]* [LOCK ] [UPSERT] [RETURN ] [WHERE ]"; + } + + @Override + public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { + return DISTRIBUTED_EXECUTION_MODE.LOCAL; + // ALWAYS EXECUTE THE COMMAND LOCALLY BECAUSE THERE IS NO A DISTRIBUTED UNDO WITH SHARDING + // + // if (distributedMode == null) + // // REPLICATE MODE COULD BE MORE EFFICIENT ON MASSIVE UPDATES + // distributedMode = upsertMode || query == null || getDatabase().getTransaction().isActive() ? DISTRIBUTED_EXECUTION_MODE.LOCAL + // : DISTRIBUTED_EXECUTION_MODE.REPLICATE; + // return distributedMode; + } + + @Override + public DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() { + return DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS; + } + + @Override + public void end() { + } + + @Override + public int getSecurityOperationType() { + return ORole.PERMISSION_UPDATE; + } + + protected void parseMerge() { + if (!parserIsEnded() && !parserGetLastWord().equals(KEYWORD_WHERE)) { + final String contentAsString = parserRequiredWord(false, "document to merge expected").trim(); + merge = new ODocument().fromJSON(contentAsString); + parserSkipWhiteSpaces(); + } + + if (merge == null) + throwSyntaxErrorException("Document to merge not provided. Example: MERGE { \"name\": \"Jay\" }"); + } + + protected String getBlock(String fieldValue) { + final int startPos = parserGetCurrentPosition(); + + if (fieldValue.startsWith("{") || fieldValue.startsWith("[")) { + if (startPos > 0) + parserSetCurrentPosition(startPos - fieldValue.length()); + else + parserSetCurrentPosition(parserText.length() - fieldValue.length()); + + parserSkipWhiteSpaces(); + final StringBuilder buffer = new StringBuilder(); + parserSetCurrentPosition(OStringSerializerHelper + .parse(parserText, buffer, parserGetCurrentPosition(), -1, OStringSerializerHelper.DEFAULT_FIELD_SEPARATOR, true, true, + false, -1, false, OStringSerializerHelper.DEFAULT_IGNORE_CHARS)); + fieldValue = buffer.toString(); + } + return fieldValue; + } + + /** + * Parses the returning keyword if found. + */ + protected void parseReturn() throws OCommandSQLParsingException { + parserNextWord(false, " "); + String mode = parserGetLastWord().trim(); + + if (mode.equalsIgnoreCase("COUNT")) { + returnHandler = new ORecordCountHandler(); + } else if (mode.equalsIgnoreCase("BEFORE") || mode.equalsIgnoreCase("AFTER")) { + + parserNextWord(false, " "); + String returning = parserGetLastWord().trim(); + Object returnExpression = null; + if (returning.equalsIgnoreCase(KEYWORD_WHERE) || returning.equalsIgnoreCase(KEYWORD_TIMEOUT) || returning + .equalsIgnoreCase(KEYWORD_LIMIT) || returning.equalsIgnoreCase(KEYWORD_UPSERT) || returning.equalsIgnoreCase(KEYWORD_LOCK) + || returning.length() == 0) { + parserGoBack(); + } else { + if (returning.startsWith("$") || returning.startsWith("@")) + returnExpression = (returning.length() > 0) ? OSQLHelper.parseValue(this, returning, this.getContext()) : null; + else + throwSyntaxErrorException("record attribute (@attributes) or functions with $current variable expected"); + } + + if (mode.equalsIgnoreCase("BEFORE")) + returnHandler = new OOriginalRecordsReturnHandler(returnExpression, getContext()); + else + returnHandler = new OUpdatedRecordsReturnHandler(returnExpression, getContext()); + + } else + throwSyntaxErrorException(" COUNT | BEFORE | AFTER keywords expected"); + } + + private boolean handleContent(ODocument record) { + boolean updated = false; + if (content != null) { + // REPLACE ALL THE CONTENT + final ODocument fieldsToPreserve = new ODocument(); + + final OClass restricted = getDatabase().getMetadata().getSchema().getClass(OSecurity.RESTRICTED_CLASSNAME); + + if (restricted != null && restricted.isSuperClassOf(record.getSchemaClass())) { + for (OProperty prop : restricted.properties()) { + fieldsToPreserve.field(prop.getName(), record.field(prop.getName())); + } + } + + OClass recordClass = ODocumentInternal.getImmutableSchemaClass(record); + if (recordClass != null && recordClass.isSubClassOf("V")) { + for (String fieldName : record.fieldNames()) { + if (fieldName.startsWith("in_") || fieldName.startsWith("out_")) { + fieldsToPreserve.field(fieldName, record.field(fieldName)); + } + } + } else if (recordClass != null && recordClass.isSubClassOf("E")) { + for (String fieldName : record.fieldNames()) { + if (fieldName.equals("in") || fieldName.equals("out")) { + fieldsToPreserve.field(fieldName, record.field(fieldName)); + } + } + } + record.merge(fieldsToPreserve, false, false); + record.merge(content, true, false); + + updated = true; + } + return updated; + } + + private boolean handleMerge(ODocument record) { + boolean updated = false; + if (merge != null) { + // MERGE THE CONTENT + record.merge(merge, true, false); + updated = true; + } + return updated; + } + + private boolean handleSetEntries(final ODocument record) { + boolean updated = false; + // BIND VALUES TO UPDATE + if (!setEntries.isEmpty()) { + OSQLHelper.bindParameters(record, setEntries, parameters, context); + updated = true; + } + return updated; + } + + private boolean handleIncrementEntries(final ODocument record) { + boolean updated = false; + // BIND VALUES TO INCREMENT + if (!incrementEntries.isEmpty()) { + for (OPair entry : incrementEntries) { + final Number prevValue = record.field(entry.getKey()); + + Number current; + if (entry.getValue() instanceof OSQLFilterItem) + current = (Number) ((OSQLFilterItem) entry.getValue()).getValue(record, null, context); + else if (entry.getValue() instanceof Number) + current = (Number) entry.getValue(); + else + throw new OCommandExecutionException("Increment value is not a number (" + entry.getValue() + ")"); + + if (prevValue == null) + // NO PREVIOUS VALUE: CONSIDER AS 0 + record.field(entry.getKey(), current); + else + // COMPUTING INCREMENT + record.field(entry.getKey(), OType.increment(prevValue, current)); + } + updated = true; + } + return updated; + } + + private boolean handleAddEntries(ODocument record) { + boolean updated = false; + // BIND VALUES TO ADD + Object fieldValue; + for (OPair entry : addEntries) { + Collection coll = null; + ORidBag bag = null; + if (!record.containsField(entry.getKey())) { + // GET THE TYPE IF ANY + if (ODocumentInternal.getImmutableSchemaClass(record) != null) { + OProperty prop = ODocumentInternal.getImmutableSchemaClass(record).getProperty(entry.getKey()); + if (prop != null && prop.getType() == OType.LINKSET) + // SET TYPE + coll = new HashSet(); + if (prop != null && prop.getType() == OType.LINKBAG) { + // there is no ridbag value already but property type is defined as LINKBAG + bag = new ORidBag(); + bag.setOwner(record); + record.field(entry.getKey(), bag); + } + } + if (coll == null && bag == null) + // IN ALL OTHER CASES USE A LIST + coll = new ArrayList(); + if (coll != null) { + // containField's condition above does NOT check subdocument's fields so + Collection currColl = record.field(entry.getKey()); + if (currColl == null) { + record.field(entry.getKey(), coll); + coll = record.field(entry.getKey()); + } else + coll = currColl; + } + + } else { + fieldValue = record.field(entry.getKey()); + + if (fieldValue instanceof Collection) + coll = (Collection) fieldValue; + else if (fieldValue instanceof ORidBag) + bag = (ORidBag) fieldValue; + else if (fieldValue == null) { + OProperty prop = record.getSchemaClass().getProperty(entry.getKey()); + if (prop == null) { + coll = new ArrayList(); + record.field(entry.getKey(), coll); + } else if (prop.getType() == OType.EMBEDDEDSET || prop.getType() == OType.LINKSET) { + coll = new LinkedHashSet(); + record.field(entry.getKey(), coll); + } else if (prop.getType() == OType.EMBEDDEDLIST || prop.getType() == OType.LINKLIST) { + coll = new ArrayList(); + record.field(entry.getKey(), coll); + } else if (prop.getType() == OType.LINKBAG) { + bag = new ORidBag(); + record.field(entry.getKey(), bag); + } else { + continue; + } + } else + continue; + } + + final Object value = extractValue(record, entry); + + if (coll != null) { + if (value instanceof OIdentifiable) + coll.add(value); + else + OMultiValue.add(coll, value); + } else { + if (!(value instanceof OIdentifiable)) + throw new OCommandExecutionException("Only links or records can be added to LINKBAG"); + + bag.add((OIdentifiable) value); + } + updated = true; + } + return updated; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private boolean handlePutEntries(ODocument record) { + boolean updated = false; + if (!putEntries.isEmpty()) { + // BIND VALUES TO PUT (AS MAP) + for (OTriple entry : putEntries) { + Object fieldValue = record.field(entry.getKey()); + + if (fieldValue == null) { + if (ODocumentInternal.getImmutableSchemaClass(record) != null) { + final OProperty property = ODocumentInternal.getImmutableSchemaClass(record).getProperty(entry.getKey()); + if (property != null && (property.getType() != null && (!property.getType().equals(OType.EMBEDDEDMAP) && !property + .getType().equals(OType.LINKMAP)))) { + throw new OCommandExecutionException("field " + entry.getKey() + " is not defined as a map"); + } + } + fieldValue = new HashMap(); + record.field(entry.getKey(), fieldValue); + } + + if (fieldValue instanceof Map) { + Map map = (Map) fieldValue; + + OPair pair = entry.getValue(); + + Object value = extractValue(record, pair); + + if (record.getSchemaClass() != null) { + final OProperty property = record.getSchemaClass().getProperty(entry.getKey()); + if (property != null && property.getType().equals(OType.LINKMAP) && !(value instanceof OIdentifiable)) { + throw new OCommandExecutionException("field " + entry.getKey() + " defined of type LINKMAP accept only link values"); + } + } + if (OType.LINKMAP.equals(OType.getTypeByValue(fieldValue)) && !(value instanceof OIdentifiable)) { + map = new OTrackedMap(record, map, Object.class); + record.field(entry.getKey(), map, OType.EMBEDDEDMAP); + } + map.put(pair.getKey(), value); + updated = true; + } + } + } + return updated; + } + + private boolean handleRemoveEntries(ODocument record) { + boolean updated = false; + if (!removeEntries.isEmpty()) { + // REMOVE FIELD IF ANY + for (OPair entry : removeEntries) { + Object value = extractValue(record, entry); + + if (value == EMPTY_VALUE) { + record.removeField(entry.getKey()); + updated = true; + } else { + final Object fieldValue = record.field(entry.getKey()); + + if (fieldValue instanceof Collection) { + updated = removeFromCollection(updated, value, (Collection) fieldValue); + } else if (fieldValue instanceof Map) { + updated = removeFromMap(updated, value, (Map) fieldValue); + } else if (fieldValue instanceof ORidBag) { + updated = removeFromBag(record, updated, value, (ORidBag) fieldValue); + } + } + } + } + return updated; + } + + private boolean removeFromCollection(boolean updated, Object value, Collection collection) { + if (value instanceof Collection) + updated |= collection.removeAll(((Collection) value)); + else + updated |= collection.remove(value); + return updated; + } + + private boolean removeFromMap(boolean updated, Object value, Map map) { + if (value instanceof Collection) { + for (Object o : ((Collection) value)) { + updated |= map.remove(o) != null; + } + } else + updated |= map.remove(value) != null; + return updated; + } + + private boolean removeFromBag(ODocument record, boolean updated, Object value, ORidBag bag) { + if (value instanceof Collection) { + for (Object o : ((Collection) value)) { + updated |= removeSingleValueFromBag(bag, o, record); + } + } else + updated |= removeSingleValueFromBag(bag, value, record); + return updated; + } + + private boolean removeSingleValueFromBag(ORidBag bag, Object value, ODocument record) { + if (!(value instanceof OIdentifiable)) + throw new OCommandExecutionException("Only links or records can be removed from LINKBAG"); + + bag.remove((OIdentifiable) value); + return record.isDirty(); + } + + private Object extractValue(ODocument record, OPair entry) { + Object value = entry.getValue(); + + if (value instanceof OSQLFilterItem) + value = ((OSQLFilterItem) value).getValue(record, null, context); + else if (value instanceof OCommandRequest) + value = ((OCommandRequest) value).execute(record, null, context); + + if (value instanceof OIdentifiable && ((OIdentifiable) value).getIdentity().isPersistent()) + // USE ONLY THE RID TO AVOID CONCURRENCY PROBLEM WITH OLD VERSIONS + value = ((OIdentifiable) value).getIdentity(); + return value; + } + + private void parseAddFields(OClass iClass) { + String fieldName; + String fieldValue; + + boolean firstLap = true; + while (!parserIsEnded() && (firstLap || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',') + && !parserGetLastWord().equals(KEYWORD_WHERE)) { + + fieldName = parserRequiredWord(false, "Field name expected"); + parserRequiredKeyword("="); + fieldValue = parserRequiredWord(false, "Value expected", " =><,\r\n", true); + + Object fVal = getFieldValueCountingParameters(fieldValue); + fVal = reattachInTx(fVal); + final Object v = convertValue(this.clazz, fieldName, fVal); + + // INSERT TRANSFORMED FIELD VALUE + addEntries.add(new OPair(fieldName, v)); + parserSkipWhiteSpaces(); + + firstLap = false; + } + + if (addEntries.size() == 0) + throwSyntaxErrorException("Entries to add = are missed. Example: name = 'Bill', salary = 300.2."); + } + + private void parsePutFields() { + String fieldName; + String fieldKey; + String fieldValue; + + boolean firstLap = true; + while (!parserIsEnded() && (firstLap || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',') + && !parserGetLastWord().equals(KEYWORD_WHERE)) { + + fieldName = parserRequiredWord(false, "Field name expected"); + parserRequiredKeyword("="); + fieldKey = parserRequiredWord(false, "Key expected"); + fieldValue = getBlock(parserRequiredWord(false, "Value expected", " =><,\r\n", true)); + + // INSERT TRANSFORMED FIELD VALUE + Object val = getFieldValueCountingParameters(fieldValue); + val = reattachInTx(val); + putEntries.add(new OTriple(fieldName, (String) getFieldValueCountingParameters(fieldKey), val)); + parserSkipWhiteSpaces(); + + firstLap = false; + } + + if (putEntries.size() == 0) + throwSyntaxErrorException("Entries to put = , are missed. Example: name = 'Bill', 30"); + } + + private void parseRemoveFields() { + String fieldName; + String fieldValue; + Object value; + + boolean firstLap = true; + while (!parserIsEnded() && (firstLap || parserGetLastSeparator() == ',' || parserGetCurrentChar() == ',') + && !parserGetLastWord().equals(KEYWORD_WHERE)) { + + fieldName = parserRequiredWord(false, "Field name expected"); + final boolean found = parserOptionalKeyword("=", "WHERE", "SET", "ADD", "REMOVE", "PUT", "MERGE", "INCREMENT"); + if (found) + if (parserGetLastWord().equals("WHERE") || parserGetLastWord().equals("SET") || parserGetLastWord().equals("ADD") + || parserGetLastWord().equals("PUT") || parserGetLastWord().equals("REMOVE") || parserGetLastWord().equals("MERGE") + || parserGetLastWord().equals("INCREMENT")) { + parserGoBack(); + value = EMPTY_VALUE; + } else { + fieldValue = getBlock(parserRequiredWord(false, "Value expected", " =><,\r\n", true)); + value = getFieldValueCountingParameters(fieldValue); + } + else + value = EMPTY_VALUE; + + // INSERT FIELD NAME TO BE REMOVED + removeEntries.add(new OPair(fieldName, value)); + parserSkipWhiteSpaces(); + + firstLap = false; + } + + if (removeEntries.size() == 0) + throwSyntaxErrorException("Field(s) to remove are missed. Example: name, salary"); + } + + private void parseIncrementFields() { + String fieldName; + String fieldValue; + + boolean firstLap = true; + while (!parserIsEnded() && (firstLap || parserGetLastSeparator() == ',') && !parserGetLastWord().equals(KEYWORD_WHERE)) { + + fieldName = parserRequiredWord(false, "Field name expected"); + parserRequiredKeyword("="); + fieldValue = getBlock(parserRequiredWord(false, "Value expected")); + + // INSERT TRANSFORMED FIELD VALUE + incrementEntries.add(new OPair(fieldName, getFieldValueCountingParameters(fieldValue))); + parserSkipWhiteSpaces(); + + firstLap = false; + } + + if (incrementEntries.size() == 0) + throwSyntaxErrorException("Entries to increment = are missed. Example: salary = -100"); + } + + @Override + public QUORUM_TYPE getQuorumType() { + return QUORUM_TYPE.WRITE; + } + + @Override + public Object getResult() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorToOStatementWrapper.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorToOStatementWrapper.java new file mode 100644 index 00000000000..2e2ebd16830 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandExecutorToOStatementWrapper.java @@ -0,0 +1,142 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.command.OCommandRequest; +import com.orientechnologies.orient.core.command.OCommandRequestText; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.parser.OStatement; +import com.orientechnologies.orient.core.sql.parser.OStatementCache; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Wrapper for OPrifileStorageStatement command (for compatibility with the old executor architecture, + * this component should be removed) + * + * @author Luigi Dell'Aquila + */ +public class OCommandExecutorToOStatementWrapper implements OCommandExecutor { + + protected OSQLAsynchQuery request; + private OCommandContext context; + private OProgressListener progressListener; + + protected OStatement statement; + + @SuppressWarnings("unchecked") + @Override + public OCommandExecutorToOStatementWrapper parse(OCommandRequest iCommand) { + final OCommandRequestText textRequest = (OCommandRequestText) iCommand; + if (iCommand instanceof OSQLAsynchQuery) { + request = (OSQLAsynchQuery) iCommand; + } else { + // BUILD A QUERY OBJECT FROM THE COMMAND REQUEST + request = new OSQLSynchQuery(textRequest.getText()); + if (textRequest.getResultListener() != null) { + request.setResultListener(textRequest.getResultListener()); + } + } + String queryText = textRequest.getText(); + statement = OStatementCache.get(queryText, getDatabase()); + return this; + } + + public static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + @Override + public Object execute(Map iArgs) { + return statement.execute(request, context, this.progressListener); + } + + @Override public RET setProgressListener(OProgressListener progressListener) { + this.progressListener = progressListener; + return (RET) this; + } + + @Override public RET setLimit(int iLimit) { + return (RET) this; + } + + @Override public String getFetchPlan() { + return null; + } + + @Override public Map getParameters() { + return null; + } + + @Override public OCommandContext getContext() { + return this.context; + } + + @Override public void setContext(OCommandContext context) { + this.context = context; + } + + @Override public boolean isIdempotent() { + return false; + } + + @Override public Set getInvolvedClusters() { + return Collections.EMPTY_SET; + } + + @Override public int getSecurityOperationType() { + return ORole.PERMISSION_READ; + } + + @Override public boolean involveSchema() { + return false; + } + + @Override public String getSyntax() { + return "PROFILE STORAGE [ON | OFF]"; + } + + @Override public boolean isLocalExecution() { + return true; + } + + @Override public boolean isCacheable() { + return false; + } + + @Override public long getDistributedTimeout() { + return 0; + } + + @Override public Object mergeResults(Map results) throws Exception { + return null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandParameters.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandParameters.java new file mode 100644 index 00000000000..e11c1b70c57 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandParameters.java @@ -0,0 +1,74 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Container of arguments. Manages also the ordinal arguments. + * + * @author Luca Garulli + * + */ +public class OCommandParameters implements Iterable> { + private final Map parameters; + private int counter = 0; + + public OCommandParameters() { + parameters = new HashMap(); + } + + public OCommandParameters(final Map iArgs) { + if (iArgs != null) + parameters = iArgs; + else + parameters = new HashMap(); + } + + public void set(final Object k, final Object v) { + parameters.put(k, v); + } + + public Object getByName(final Object iName) { + return parameters.get(iName); + } + + public Object getNext() { + if (parameters.size() <= counter) + throw new IndexOutOfBoundsException("Parameter " + counter + " not found. Total parameters received: " + parameters.size()); + + return parameters.get(counter++); + } + + public Iterator> iterator() { + return parameters.entrySet().iterator(); + } + + public int size() { + return parameters.size(); + } + + public void reset() { + counter = 0; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQL.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQL.java new file mode 100644 index 00000000000..1fde8e369f6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQL.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandRequestTextAbstract; +import com.orientechnologies.orient.core.replication.OAsyncReplicationError; +import com.orientechnologies.orient.core.replication.OAsyncReplicationOk; + +/** + * SQL command request implementation. It just stores the request and delegated the execution to the configured OCommandExecutor. + * + * @author Luca Garulli + */ +@SuppressWarnings("serial") +public class OCommandSQL extends OCommandRequestTextAbstract { + public OCommandSQL() { + } + + public OCommandSQL(final String iText) { + super(iText); + } + + public boolean isIdempotent() { + return false; + } + + @Override + public String toString() { + return "sql." + text;// OIOUtils.getStringMaxLength(text, 50, "..."); + } + + /** + * Defines a callback to call in case of the asynchronous replication succeed. + */ + @Override + public OCommandSQL onAsyncReplicationOk(final OAsyncReplicationOk iCallback) { + return (OCommandSQL) super.onAsyncReplicationOk(iCallback); + } + + /** + * Defines a callback to call in case of error during the asynchronous replication. + */ + @Override + public OCommandSQL onAsyncReplicationError(final OAsyncReplicationError iCallback) { + return (OCommandSQL) super.onAsyncReplicationError(iCallback); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLParsingException.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLParsingException.java new file mode 100755 index 00000000000..cf39dc0b3d7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLParsingException.java @@ -0,0 +1,113 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.exception.OCoreException; +import com.orientechnologies.orient.core.sql.parser.ParseException; + +public class OCommandSQLParsingException extends OCoreException { + + private Integer line; + private Integer column; + private String statement; + private String text; + private int position; + private static final long serialVersionUID = -7430575036316163711L; + + public OCommandSQLParsingException(ParseException e, String statement) { + super(generateMessage(e, statement, e.currentToken.next.beginLine, e.currentToken.next.endColumn)); + this.statement = statement; + this.line = e.currentToken.next.beginLine; + this.column = e.currentToken.next.endColumn; + } + + private static String generateMessage(ParseException e, String statement, Integer line, Integer column) { + StringBuilder result = new StringBuilder(); + result.append("Error parsing query:\n"); + String[] stmLines = statement.split("\n"); + for (int i = 0; i < stmLines.length; i++) { + result.append(stmLines[i]); + result.append("\n"); + if (i == line - 1) { + for (int c = 0; c < column - 1; c++) { + result.append(' '); + } + result.append("^\n"); + } + } + result.append(e.getMessage()); + return result.toString(); + } + + private static String makeMessage(int position, String text, String message) { + StringBuilder buffer = new StringBuilder(); + buffer.append("Error on parsing command"); + buffer.append(": ").append(message); + + if (text != null) { + buffer.append("\nCommand: "); + buffer.append(text); + buffer.append("\n---------"); + for (int i = 0; i < position - 1; ++i) + buffer.append("-"); + + buffer.append("^"); + } + return buffer.toString(); + } + + public OCommandSQLParsingException(OCommandSQLParsingException exception) { + super(exception); + + this.text = exception.text; + this.position = exception.position; + } + + public OCommandSQLParsingException(String iMessage) { + super(iMessage); + } + + public OCommandSQLParsingException(String iMessage, String iText, int iPosition) { + super(makeMessage(iPosition, iText, iMessage)); + + text = iText; + position = iPosition; + } + + public Integer getLine() { + return line; + } + + public Integer getColumn() { + return column; + } + + public String getStatement() { + return statement; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || !(obj instanceof OCommandSQLParsingException)) + return false; + + return toString().equals(obj.toString()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLResultset.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLResultset.java new file mode 100644 index 00000000000..aee9d144742 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OCommandSQLResultset.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +/** + * Iterable SQL command request implementation. + * + * @author Luca Garulli + * + */ +@SuppressWarnings("serial") +public class OCommandSQLResultset extends OCommandSQL { + public OCommandSQLResultset() { + } + + public OCommandSQLResultset(final String iText) { + super(iText); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/ODefaultCommandExecutorSQLFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/ODefaultCommandExecutorSQLFactory.java new file mode 100755 index 00000000000..347bb75eb15 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/ODefaultCommandExecutorSQLFactory.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.parser.OMatchStatement; +import com.orientechnologies.orient.core.sql.parser.OProfileStorageStatement; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Default command operator executor factory. + * + * @author Johann Sorel (Geomatys) + */ +public class ODefaultCommandExecutorSQLFactory implements OCommandExecutorSQLFactory { + + private static final Map> COMMANDS; + + static { + + // COMMANDS + final Map> commands = new HashMap>(); + commands.put(OCommandExecutorSQLAlterDatabase.KEYWORD_ALTER + " " + OCommandExecutorSQLAlterDatabase.KEYWORD_DATABASE, + OCommandExecutorSQLAlterDatabase.class); + commands.put(OCommandExecutorSQLSelect.KEYWORD_SELECT, OCommandExecutorSQLSelect.class); + commands.put(OCommandExecutorSQLTraverse.KEYWORD_TRAVERSE, OCommandExecutorSQLTraverse.class); + commands.put(OCommandExecutorSQLInsert.KEYWORD_INSERT, OCommandExecutorSQLInsert.class); + commands.put(OCommandExecutorSQLUpdate.KEYWORD_UPDATE, OCommandExecutorSQLUpdate.class); + commands.put(OCommandExecutorSQLDelete.NAME, OCommandExecutorSQLDelete.class); + commands.put(OCommandExecutorSQLHide.NAME, OCommandExecutorSQLHide.class); + commands.put(OCommandExecutorSQLCreateFunction.NAME, OCommandExecutorSQLCreateFunction.class); + commands.put(OCommandExecutorSQLGrant.KEYWORD_GRANT, OCommandExecutorSQLGrant.class); + commands.put(OCommandExecutorSQLRevoke.KEYWORD_REVOKE, OCommandExecutorSQLRevoke.class); + commands.put(OCommandExecutorSQLCreateLink.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateLink.KEYWORD_LINK, + OCommandExecutorSQLCreateLink.class); + commands.put(OCommandExecutorSQLCreateIndex.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateIndex.KEYWORD_INDEX, + OCommandExecutorSQLCreateIndex.class); + commands.put(OCommandExecutorSQLDropIndex.KEYWORD_DROP + " " + OCommandExecutorSQLDropIndex.KEYWORD_INDEX, + OCommandExecutorSQLDropIndex.class); + commands.put(OCommandExecutorSQLRebuildIndex.KEYWORD_REBUILD + " " + OCommandExecutorSQLRebuildIndex.KEYWORD_INDEX, + OCommandExecutorSQLRebuildIndex.class); + commands.put(OCommandExecutorSQLCreateClass.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateClass.KEYWORD_CLASS, + OCommandExecutorSQLCreateClass.class); + commands.put(OCommandExecutorSQLCreateCluster.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateCluster.KEYWORD_CLUSTER, + OCommandExecutorSQLCreateCluster.class); + commands.put(OCommandExecutorSQLCreateCluster.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateCluster.KEYWORD_BLOB + " " + + OCommandExecutorSQLCreateCluster.KEYWORD_CLUSTER, + OCommandExecutorSQLCreateCluster.class); + commands.put(OCommandExecutorSQLAlterClass.KEYWORD_ALTER + " " + OCommandExecutorSQLAlterClass.KEYWORD_CLASS, + OCommandExecutorSQLAlterClass.class); + commands.put(OCommandExecutorSQLCreateProperty.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateProperty.KEYWORD_PROPERTY, + OCommandExecutorSQLCreateProperty.class); + commands.put(OCommandExecutorSQLAlterProperty.KEYWORD_ALTER + " " + OCommandExecutorSQLAlterProperty.KEYWORD_PROPERTY, + OCommandExecutorSQLAlterProperty.class); + commands.put(OCommandExecutorSQLDropCluster.KEYWORD_DROP + " " + OCommandExecutorSQLDropCluster.KEYWORD_CLUSTER, + OCommandExecutorSQLDropCluster.class); + commands.put(OCommandExecutorSQLDropClass.KEYWORD_DROP + " " + OCommandExecutorSQLDropClass.KEYWORD_CLASS, + OCommandExecutorSQLDropClass.class); + commands.put(OCommandExecutorSQLDropProperty.KEYWORD_DROP + " " + OCommandExecutorSQLDropProperty.KEYWORD_PROPERTY, + OCommandExecutorSQLDropProperty.class); + commands.put(OCommandExecutorSQLFindReferences.KEYWORD_FIND + " " + OCommandExecutorSQLFindReferences.KEYWORD_REFERENCES, + OCommandExecutorSQLFindReferences.class); + commands.put(OCommandExecutorSQLTruncateClass.KEYWORD_TRUNCATE + " " + OCommandExecutorSQLTruncateClass.KEYWORD_CLASS, + OCommandExecutorSQLTruncateClass.class); + commands.put(OCommandExecutorSQLTruncateCluster.KEYWORD_TRUNCATE + " " + OCommandExecutorSQLTruncateCluster.KEYWORD_CLUSTER, + OCommandExecutorSQLTruncateCluster.class); + commands.put(OCommandExecutorSQLTruncateRecord.KEYWORD_TRUNCATE + " " + OCommandExecutorSQLTruncateRecord.KEYWORD_RECORD, + OCommandExecutorSQLTruncateRecord.class); + commands.put(OCommandExecutorSQLAlterCluster.KEYWORD_ALTER + " " + OCommandExecutorSQLAlterCluster.KEYWORD_CLUSTER, + OCommandExecutorSQLAlterCluster.class); + commands.put(OCommandExecutorSQLCreateSequence.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateSequence.KEYWORD_SEQUENCE, + OCommandExecutorSQLCreateSequence.class); + commands.put(OCommandExecutorSQLAlterSequence.KEYWORD_ALTER + " " + OCommandExecutorSQLAlterSequence.KEYWORD_SEQUENCE, + OCommandExecutorSQLAlterSequence.class); + commands.put(OCommandExecutorSQLDropSequence.KEYWORD_DROP + " " + OCommandExecutorSQLDropSequence.KEYWORD_SEQUENCE, + OCommandExecutorSQLDropSequence.class); + commands.put(OCommandExecutorSQLCreateUser.KEYWORD_CREATE + " " + OCommandExecutorSQLCreateUser.KEYWORD_USER, + OCommandExecutorSQLCreateUser.class); + commands.put(OCommandExecutorSQLDropUser.KEYWORD_DROP + " " + OCommandExecutorSQLDropUser.KEYWORD_USER, + OCommandExecutorSQLDropUser.class); + commands.put(OCommandExecutorSQLExplain.KEYWORD_EXPLAIN, OCommandExecutorSQLExplain.class); + commands.put(OCommandExecutorSQLTransactional.KEYWORD_TRANSACTIONAL, OCommandExecutorSQLTransactional.class); + + commands.put(OMatchStatement.KEYWORD_MATCH, OMatchStatement.class); + commands.put(OCommandExecutorSQLOptimizeDatabase.KEYWORD_OPTIMIZE, OCommandExecutorSQLOptimizeDatabase.class); + + commands.put(OProfileStorageStatement.KEYWORD_PROFILE, OCommandExecutorToOStatementWrapper.class); + + COMMANDS = Collections.unmodifiableMap(commands); + } + + /** + * {@inheritDoc} + */ + public Set getCommandNames() { + return COMMANDS.keySet(); + } + + /** + * {@inheritDoc} + */ + public OCommandExecutor createCommand(final String name) throws OCommandExecutionException { + final Class clazz = COMMANDS.get(name); + + if (clazz == null) { + throw new OCommandExecutionException("Unknowned command name :" + name); + } + + try { + return clazz.newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Error in creation of command " + name + + "(). Probably there is not an empty constructor or the constructor generates errors"), e); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/ODynamicSQLElementFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/ODynamicSQLElementFactory.java new file mode 100755 index 00000000000..0398366f3e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/ODynamicSQLElementFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionFactory; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorFactory; + +/** + * Dynamic sql elements factory. + * + * @author Johann Sorel (Geomatys) + */ +public class ODynamicSQLElementFactory implements OCommandExecutorSQLFactory, OQueryOperatorFactory, OSQLFunctionFactory { + + // Used by SQLEngine to register on the fly new elements + static final Map FUNCTIONS = new ConcurrentHashMap(); + static final Map> COMMANDS = new ConcurrentHashMap>(); + static final Set OPERATORS = Collections + .synchronizedSet(new HashSet()); + + public Set getFunctionNames() { + return FUNCTIONS.keySet(); + } + + public boolean hasFunction(final String name) { + return FUNCTIONS.containsKey(name); + } + + public OSQLFunction createFunction(final String name) throws OCommandExecutionException { + final Object obj = FUNCTIONS.get(name); + + if (obj == null) { + throw new OCommandExecutionException("Unknown function name :" + name); + } + + if (obj instanceof OSQLFunction) { + return (OSQLFunction) obj; + } else { + // it's a class + final Class clazz = (Class) obj; + try { + return (OSQLFunction) clazz.newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Error in creation of function " + name + + "(). Probably there is not an empty constructor or the constructor generates errors"), e); + } + } + } + + public Set getCommandNames() { + return COMMANDS.keySet(); + } + + public OCommandExecutorSQLAbstract createCommand(final String name) throws OCommandExecutionException { + final Class clazz = COMMANDS.get(name); + + if (clazz == null) + throw new OCommandExecutionException("Unknown command name :" + name); + + try { + return clazz.newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Error in creation of command " + name + + "(). Probably there is not an empty constructor or the constructor generates errors"), e); + } + } + + public Set getOperators() { + return OPERATORS; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OFilterAnalyzer.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OFilterAnalyzer.java new file mode 100644 index 00000000000..34f13ffa482 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OFilterAnalyzer.java @@ -0,0 +1,259 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.operator.*; + +import java.util.*; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OFilterAnalyzer { + + public List> getInvolvedIndexes(OClass iSchemaClass, OIndexSearchResult searchResultFields) { + final Set> involvedIndexes = iSchemaClass.getInvolvedIndexes(searchResultFields.fields()); + + final List> result = new ArrayList>(involvedIndexes.size()); + + if (searchResultFields.lastField.isLong()) { + result.addAll(OChainedIndexProxy.createProxies(iSchemaClass, searchResultFields.lastField)); + } else { + for (OIndex involvedIndex : involvedIndexes) { + result.add(involvedIndex); + } + } + + return result; + } + + public List> analyzeMainCondition(OSQLFilterCondition condition, final OClass schemaClass, + OCommandContext context) { + return analyzeOrFilterBranch(schemaClass, condition, context); + } + + private List> analyzeOrFilterBranch(final OClass iSchemaClass, OSQLFilterCondition condition, + OCommandContext iContext) { + if (condition == null) { + return null; + } + + OQueryOperator operator = condition.getOperator(); + + while (operator == null) { + if (condition.getRight() == null && condition.getLeft() instanceof OSQLFilterCondition) { + condition = (OSQLFilterCondition) condition.getLeft(); + operator = condition.getOperator(); + } else { + return null; + } + } + + final OIndexReuseType indexReuseType = operator.getIndexReuseType(condition.getLeft(), condition.getRight()); + if (OIndexReuseType.INDEX_UNION.equals(indexReuseType)) { + return analyzeUnion(iSchemaClass, condition, iContext); + } + + List> result = new ArrayList>(); + List sub = analyzeCondition(condition, iSchemaClass, iContext); + // analyzeFilterBranch(iSchemaClass, condition, sub, iContext); + result.add(sub); + return result; + } + + /** + * Analyzes a query filter for a possible indexation options. The results are sorted by amount of fields. So the most specific + * items go first. + * + * @param condition to analyze + * @param schemaClass the class that is scanned by query + * @param context of the query + * @return list of OIndexSearchResult items + */ + public List analyzeCondition(OSQLFilterCondition condition, final OClass schemaClass, + OCommandContext context) { + + final List indexSearchResults = new ArrayList(); + OIndexSearchResult lastCondition = analyzeFilterBranch(schemaClass, condition, indexSearchResults, context); + + if (indexSearchResults.isEmpty() && lastCondition != null) { + indexSearchResults.add(lastCondition); + } + Collections.sort(indexSearchResults, new Comparator() { + public int compare(final OIndexSearchResult searchResultOne, final OIndexSearchResult searchResultTwo) { + return searchResultTwo.getFieldCount() - searchResultOne.getFieldCount(); + } + }); + + return indexSearchResults; + } + + private OIndexSearchResult analyzeFilterBranch(final OClass iSchemaClass, OSQLFilterCondition condition, + final List iIndexSearchResults, OCommandContext iContext) { + if (condition == null) { + return null; + } + + OQueryOperator operator = condition.getOperator(); + + while (operator == null) { + if (condition.getRight() == null && condition.getLeft() instanceof OSQLFilterCondition) { + condition = (OSQLFilterCondition) condition.getLeft(); + operator = condition.getOperator(); + } else { + return null; + } + } + + final OIndexReuseType indexReuseType = operator.getIndexReuseType(condition.getLeft(), condition.getRight()); + switch (indexReuseType) { + case INDEX_INTERSECTION: + return analyzeIntersection(iSchemaClass, condition, iIndexSearchResults, iContext); + case INDEX_METHOD: + return analyzeIndexMethod(iSchemaClass, condition, iIndexSearchResults, iContext); + case INDEX_OPERATOR: + return analyzeOperator(iSchemaClass, condition, iIndexSearchResults, iContext); + default: + return null; + } + } + + private OIndexSearchResult analyzeOperator(OClass iSchemaClass, OSQLFilterCondition condition, + List iIndexSearchResults, OCommandContext iContext) { + return condition.getOperator().getOIndexSearchResult(iSchemaClass, condition, iIndexSearchResults, iContext); + } + + private OIndexSearchResult analyzeIndexMethod(OClass iSchemaClass, OSQLFilterCondition condition, + List iIndexSearchResults, OCommandContext ctx) { + OIndexSearchResult result = createIndexedProperty(condition, condition.getLeft(), ctx); + if (result == null) { + result = createIndexedProperty(condition, condition.getRight(), ctx); + } + + if (result == null) { + return null; + } + + if (checkIndexExistence(iSchemaClass, result)) { + iIndexSearchResults.add(result); + } + + return result; + } + + private OIndexSearchResult analyzeIntersection(OClass iSchemaClass, OSQLFilterCondition condition, + List iIndexSearchResults, OCommandContext iContext) { + final OIndexSearchResult leftResult = analyzeFilterBranch(iSchemaClass, (OSQLFilterCondition) condition.getLeft(), + iIndexSearchResults, iContext); + final OIndexSearchResult rightResult = analyzeFilterBranch(iSchemaClass, (OSQLFilterCondition) condition.getRight(), + iIndexSearchResults, iContext); + + if (leftResult != null && rightResult != null) { + if (leftResult.canBeMerged(rightResult)) { + final OIndexSearchResult mergeResult = leftResult.merge(rightResult); + if (iSchemaClass.areIndexed(mergeResult.fields())) { + iIndexSearchResults.add(mergeResult); + } + + return leftResult.merge(rightResult); + } + } + + return null; + } + + private List> analyzeUnion(OClass iSchemaClass, OSQLFilterCondition condition, + OCommandContext iContext) { + List> result = new ArrayList>(); + + result.addAll(analyzeOrFilterBranch(iSchemaClass, (OSQLFilterCondition) condition.getLeft(), iContext)); + result.addAll(analyzeOrFilterBranch(iSchemaClass, (OSQLFilterCondition) condition.getRight(), iContext)); + + return result; + } + + /** + * Add SQL filter field to the search candidate list. + * + * @param iCondition Condition item + * @param iItem Value to search + * @return true if the property was indexed and found, otherwise false + */ + private OIndexSearchResult createIndexedProperty(final OSQLFilterCondition iCondition, final Object iItem, OCommandContext ctx) { + if (iItem == null || !(iItem instanceof OSQLFilterItemField)) { + return null; + } + + if (iCondition.getLeft() instanceof OSQLFilterItemField && iCondition.getRight() instanceof OSQLFilterItemField) { + return null; + } + + final OSQLFilterItemField item = (OSQLFilterItemField) iItem; + + if (item.hasChainOperators() && !item.isFieldChain()) { + return null; + } + + final Object origValue = iCondition.getLeft() == iItem ? iCondition.getRight() : iCondition.getLeft(); + + OQueryOperator operator = iCondition.getOperator(); + + if (iCondition.getRight() == iItem) { + if (operator instanceof OQueryOperatorIn) { + operator = new OQueryOperatorContains(); + } else if (operator instanceof OQueryOperatorContains) { + operator = new OQueryOperatorIn(); + } + } + + if (iCondition.getOperator() instanceof OQueryOperatorBetween || operator instanceof OQueryOperatorIn) { + + return new OIndexSearchResult(operator, item.getFieldChain(), origValue); + } + + final Object value = OSQLHelper.getValue(origValue, null, ctx); + return new OIndexSearchResult(operator, item.getFieldChain(), value); + } + + private boolean checkIndexExistence(final OClass iSchemaClass, final OIndexSearchResult result) { + return iSchemaClass.areIndexed(result.fields()) && (!result.lastField.isLong() || checkIndexChainExistence(iSchemaClass, + result)); + } + + private boolean checkIndexChainExistence(OClass iSchemaClass, OIndexSearchResult result) { + final int fieldCount = result.lastField.getItemCount(); + OClass cls = iSchemaClass.getProperty(result.lastField.getItemName(0)).getLinkedClass(); + + for (int i = 1; i < fieldCount; i++) { + if (cls == null || !cls.areIndexed(result.lastField.getItemName(i))) { + return false; + } + + cls = cls.getProperty(result.lastField.getItemName(i)).getLinkedClass(); + } + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OFindReferenceHelper.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OFindReferenceHelper.java new file mode 100644 index 00000000000..d42c2a1c546 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OFindReferenceHelper.java @@ -0,0 +1,180 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.object.OLazyObjectListInterface; +import com.orientechnologies.orient.core.db.object.OLazyObjectMapInterface; +import com.orientechnologies.orient.core.db.object.OLazyObjectSetInterface; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMap; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Helper class to find reference in records. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @author Luca Molino + * + */ +public class OFindReferenceHelper { + + public static List findReferences(final Set iRecordIds, final String classList) { + final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); + + final Map> map = new HashMap>(); + for (ORID rid : iRecordIds) { + map.put(rid, new HashSet()); + } + + if (classList == null || classList.isEmpty()) { + for (String clusterName : db.getClusterNames()) { + browseCluster(db, iRecordIds, map, clusterName); + } + } else { + final List classes = OStringSerializerHelper.smartSplit(classList, ','); + for (String clazz : classes) { + if (clazz.startsWith("CLUSTER:")) { + browseCluster(db, iRecordIds, map, clazz.substring(clazz.indexOf("CLUSTER:") + "CLUSTER:".length())); + } else { + browseClass(db, iRecordIds, map, clazz); + } + } + } + + final List result = new ArrayList(); + for (Entry> entry : map.entrySet()) { + final ODocument doc = new ODocument(); + result.add(doc); + + doc.field("rid", entry.getKey()); + doc.field("referredBy", entry.getValue()); + } + + return result; + } + + private static void browseCluster(final ODatabaseDocument iDatabase, final Set iSourceRIDs, final Map> map, + final String iClusterName) { + for (ORecord record : iDatabase.browseCluster(iClusterName)) { + if (record instanceof ODocument) { + try { + for (String fieldName : ((ODocument) record).fieldNames()) { + Object value = ((ODocument) record).field(fieldName); + checkObject(iSourceRIDs, map, value, (ODocument) record); + } + } catch (Exception e) { + OLogManager.instance().debug(null, "Error reading record " + record.getIdentity(), e); + } + } + } + } + + private static void browseClass(final ODatabaseDocument db, Set iSourceRIDs, final Map> map, + final String iClassName) { + final OClass clazz = ((OMetadataInternal)db.getMetadata()).getImmutableSchemaSnapshot().getClass(iClassName); + + if (clazz == null) + throw new OCommandExecutionException("Class '" + iClassName + "' was not found"); + + for (int i : clazz.getClusterIds()) { + browseCluster(db, iSourceRIDs, map, db.getClusterNameById(i)); + } + } + + private static void checkObject(final Set iSourceRIDs, final Map> map, final Object value, + final ORecord iRootObject) { + if (value instanceof OIdentifiable) { + checkRecord(iSourceRIDs, map, (OIdentifiable) value, iRootObject); + } else if (value instanceof Collection) { + checkCollection(iSourceRIDs, map, (Collection) value, iRootObject); + } else if (value instanceof Map) { + checkMap(iSourceRIDs, map, (Map) value, iRootObject); + } + } + + private static void checkCollection(final Set iSourceRIDs, final Map> map, final Collection values, + final ORecord iRootObject) { + final Iterator it; + if (values instanceof OLazyObjectListInterface) { + ((OLazyObjectListInterface) values).setConvertToRecord(false); + it = ((OLazyObjectListInterface) values).listIterator(); + } else if (values instanceof OLazyObjectSetInterface) { + ((OLazyObjectSetInterface) values).setConvertToRecord(false); + it = ((OLazyObjectSetInterface) values).iterator(); + } else if (values instanceof ORecordLazyMultiValue) { + it = ((ORecordLazyMultiValue) values).rawIterator(); + } else { + it = values.iterator(); + } + while (it.hasNext()) { + checkObject(iSourceRIDs, map, it.next(), iRootObject); + } + } + + private static void checkMap(final Set iSourceRIDs, final Map> map, final Map values, + final ORecord iRootObject) { + final Iterator it; + if (values instanceof OLazyObjectMapInterface) { + ((OLazyObjectMapInterface) values).setConvertToRecord(false); + it = ((OLazyObjectMapInterface) values).values().iterator(); + } else if (values instanceof ORecordLazyMap) { + it = ((ORecordLazyMap) values).rawIterator(); + } else { + it = values.values().iterator(); + } + while (it.hasNext()) { + checkObject(iSourceRIDs, map, it.next(), iRootObject); + } + } + + private static void checkRecord(final Set iSourceRIDs, final Map> map, final OIdentifiable value, + final ORecord iRootObject) { + if (iSourceRIDs.contains(value.getIdentity())) { + map.get(value.getIdentity()).add(iRootObject.getIdentity()); + }else if(!value.getIdentity().isValid() && value.getRecord() instanceof ODocument){ + //embedded document + ODocument doc = value.getRecord(); + for (String fieldName : doc.fieldNames()) { + Object fieldValue = doc.field(fieldName); + checkObject(iSourceRIDs, map, fieldValue, iRootObject); + } + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OIndexSearchResult.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OIndexSearchResult.java new file mode 100644 index 00000000000..a130486e853 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OIndexSearchResult.java @@ -0,0 +1,174 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.operator.*; + +import java.util.*; + +/** + * Presents query subset in form of field1 = "field1 value" AND field2 = "field2 value" ... AND fieldN anyOpetator "fieldN value" + *

          + * Where pairs (field1, value1) ... (fieldn-1, valuen-1) are stored in {@link #fieldValuePairs} map but last pair is stored in + * {@link #lastField} {@link #lastValue} properties and their operator will be stored in {@link #lastOperator} property. + *

          + * Such data structure is used because from composite index point of view any "field and value" pairs can be reordered to match keys + * order that is used in index in case all fields and values are related to each other using equals operator, but position of field + * - value pair that uses non equals operator cannot be changed. Actually only one non-equals operator can be used for composite + * index search and filed - value pair that uses this index should always be placed at last position. + */ +public class OIndexSearchResult { + public final Map fieldValuePairs = new HashMap(8); + public final OQueryOperator lastOperator; + public final OSQLFilterItemField.FieldChain lastField; + public final Object lastValue; + boolean containsNullValues; + + public OIndexSearchResult(final OQueryOperator lastOperator, final OSQLFilterItemField.FieldChain field, final Object value) { + this.lastOperator = lastOperator; + lastField = field; + lastValue = value; + + containsNullValues = value == null; + } + + public static boolean isIndexEqualityOperator(OQueryOperator queryOperator) { + return queryOperator instanceof OQueryOperatorEquals || queryOperator instanceof OQueryOperatorContains + || queryOperator instanceof OQueryOperatorContainsKey || queryOperator instanceof OQueryOperatorContainsValue; + } + + /** + * Combines two queries subset into one. This operation will be valid only if {@link #canBeMerged(OIndexSearchResult)} method will + * return true for the same passed in parameter. + * + * @param searchResult + * Query subset to merge. + * @return New instance that presents merged query. + */ + public OIndexSearchResult merge(final OIndexSearchResult searchResult) { + // if (searchResult.lastOperator instanceof OQueryOperatorEquals) { + if (searchResult.lastOperator instanceof OQueryOperatorEquals) { + return mergeFields(this, searchResult); + } + if (lastOperator instanceof OQueryOperatorEquals) { + return mergeFields(searchResult, this); + } + if (isIndexEqualityOperator(searchResult.lastOperator)) { + return mergeFields(this, searchResult); + } + return mergeFields(searchResult, this); + } + + private OIndexSearchResult mergeFields(OIndexSearchResult mainSearchResult, OIndexSearchResult searchResult) { + OIndexSearchResult result = new OIndexSearchResult(mainSearchResult.lastOperator, mainSearchResult.lastField, + mainSearchResult.lastValue); + result.fieldValuePairs.putAll(searchResult.fieldValuePairs); + result.fieldValuePairs.putAll(mainSearchResult.fieldValuePairs); + result.fieldValuePairs.put(searchResult.lastField.getItemName(0), searchResult.lastValue); + result.containsNullValues = searchResult.containsNullValues || this.containsNullValues; + return result; + } + + /** + * @param searchResult + * Query subset is going to be merged with given one. + * @return true if two query subsets can be merged. + */ + boolean canBeMerged(final OIndexSearchResult searchResult) { + if (lastField.isLong() || searchResult.lastField.isLong()) { + return false; + } + if (!lastOperator.canBeMerged() || !searchResult.lastOperator.canBeMerged()) { + return false; + } + return isIndexEqualityOperator(lastOperator) || isIndexEqualityOperator(searchResult.lastOperator); + } + + public List fields() { + final List result = new ArrayList(fieldValuePairs.size() + 1); + result.addAll(fieldValuePairs.keySet()); + result.add(lastField.getItemName(0)); + return result; + } + + int getFieldCount() { + return fieldValuePairs.size() + 1; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OIndexSearchResult that = (OIndexSearchResult) o; + + if (containsNullValues != that.containsNullValues) { + return false; + } + for (Map.Entry entry : fieldValuePairs.entrySet()) { + if (!that.fieldValuePairs.get(entry.getKey()).equals(entry.getValue())) { + return false; + } + } + + if (!lastField.equals(that.lastField)) { + return false; + } + if (!lastOperator.equals(that.lastOperator)) { + return false; + } + if (!lastValue.equals(that.lastValue)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = lastOperator.hashCode(); + + for (Map.Entry entry : fieldValuePairs.entrySet()) { + if (entry.getKey() != null) { + result = 31 * result + entry.getKey().hashCode(); + } + if (entry.getValue() != null) { + result = 31 * result + entry.getValue().hashCode(); + } + } + + if (lastField != null) { + result = 31 * result + lastField.hashCode(); + } + if (lastValue != null) { + result = 31 * result + lastValue.hashCode(); + } + + result = 31 * result + (containsNullValues ? 1 : 0); + return result; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OIterableRecordSource.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OIterableRecordSource.java new file mode 100644 index 00000000000..170857c2d02 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OIterableRecordSource.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import java.util.Iterator; +import java.util.Map; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public interface OIterableRecordSource { + Iterator iterator(final Map iArgs); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OLiveCommandExecutorSQLFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OLiveCommandExecutorSQLFactory.java new file mode 100755 index 00000000000..43de12bf95b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OLiveCommandExecutorSQLFactory.java @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Live Query command operator executor factory. + * + * @author Luigi Dell'Aquila + */ +public class OLiveCommandExecutorSQLFactory implements OCommandExecutorSQLFactory { + + private static Map> COMMANDS = new HashMap>(); + + static { + init(); + } + + public static void init() { + if (COMMANDS.size() == 0) { + synchronized (OLiveCommandExecutorSQLFactory.class) { + if (COMMANDS.size() == 0) { + final Map> commands = new HashMap>(); + commands.put(OCommandExecutorSQLLiveSelect.KEYWORD_LIVE_SELECT, OCommandExecutorSQLLiveSelect.class); + commands.put(OCommandExecutorSQLLiveUnsubscribe.KEYWORD_LIVE_UNSUBSCRIBE, OCommandExecutorSQLLiveUnsubscribe.class); + + COMMANDS = Collections.unmodifiableMap(commands); + } + } + } + } + + /** + * {@inheritDoc} + */ + public Set getCommandNames() { + return COMMANDS.keySet(); + } + + /** + * {@inheritDoc} + */ + public OCommandExecutorSQLAbstract createCommand(final String name) throws OCommandExecutionException { + final Class clazz = COMMANDS.get(name); + + if (clazz == null) { + throw new OCommandExecutionException("Unknowned command name :" + name); + } + + try { + return clazz.newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Error in creation of command " + name + + "(). Probably there is not an empty constructor or the constructor generates errors"), e); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OMetricRecorder.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OMetricRecorder.java new file mode 100644 index 00000000000..1d29c920d03 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OMetricRecorder.java @@ -0,0 +1,74 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import java.util.HashSet; +import java.util.Set; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.index.OIndex; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OMetricRecorder { + protected OCommandContext context; + + public void setContext(OCommandContext context) { + this.context = context; + } + + public void recordOrderByOptimizationMetric(boolean indexIsUsedInOrderBy, boolean fullySortedByIndex) { + if (context.isRecordingMetrics()) { + context.setVariable("indexIsUsedInOrderBy", indexIsUsedInOrderBy); + context.setVariable("fullySortedByIndex", fullySortedByIndex); + } + } + + public void recordInvolvedIndexesMetric(OIndex index) { + if (context.isRecordingMetrics()) { + Set idxNames = (Set) context.getVariable("involvedIndexes"); + if (idxNames == null) { + idxNames = new HashSet(); + context.setVariable("involvedIndexes", idxNames); + } + if (index instanceof OChainedIndexProxy) { + idxNames.addAll(((OChainedIndexProxy) index).getIndexNames()); + } else + idxNames.add(index.getName()); + } + } + + OCommandContext orderByElapsed(long startOrderBy) { + return context.setVariable("orderByElapsed", (System.currentTimeMillis() - startOrderBy)); + } + + public void recordRangeQueryConvertedInBetween() { + if (context.isRecordingMetrics()) { + Integer counter = (Integer) context.getVariable("rangeQueryConvertedInBetween"); + if (counter == null) + counter = 0; + + counter++; + context.setVariable("rangeQueryConvertedInBetween", counter); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OOrderByOptimizer.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OOrderByOptimizer.java new file mode 100644 index 00000000000..e051bbf4b8f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OOrderByOptimizer.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexDefinition; + +import java.util.List; +import java.util.Locale; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OOrderByOptimizer { + boolean canBeUsedByOrderBy(OIndex index, List> orderedFields) { + if (orderedFields.isEmpty()) + return false; + + if (!index.supportsOrderedIterations()) + return false; + + final OIndexDefinition definition = index.getDefinition(); + final List fields = definition.getFields(); + final int endIndex = Math.min(fields.size(), orderedFields.size()); + + final String firstOrder = orderedFields.get(0).getValue(); + for (int i = 0; i < endIndex; i++) { + final OPair pair = orderedFields.get(i); + + if (!firstOrder.equals(pair.getValue())) + return false; + + final String orderFieldName = orderedFields.get(i).getKey().toLowerCase(Locale.ENGLISH); + final String indexFieldName = fields.get(i).toLowerCase(Locale.ENGLISH); + + if (!orderFieldName.equals(indexFieldName)) + return false; + } + + return true; + } + + /** + * checks if, given a list of "=" conditions and a set of ORDER BY fields + * + * @param index + * @param equalsFilterFields + * @param orderedFields + * @return + */ + boolean canBeUsedByOrderByAfterFilter(OIndex index, List equalsFilterFields, + List> orderedFields) { + if (orderedFields.isEmpty()) + return false; + + if (!index.supportsOrderedIterations()) + return false; + + final OIndexDefinition definition = index.getDefinition(); + final List indexFields = definition.getFields(); + int endIndex = Math.min(indexFields.size(), equalsFilterFields.size()); + + final String firstOrder = orderedFields.get(0).getValue(); + + //check that all the "equals" clauses are a prefix for the index + for (int i = 0; i < endIndex; i++) { + final String equalsFieldName = equalsFilterFields.get(i).toLowerCase(Locale.ENGLISH); + final String indexFieldName = indexFields.get(i).toLowerCase(Locale.ENGLISH); + if (!equalsFieldName.equals(indexFieldName)) + return false; + } + + endIndex = Math.min(indexFields.size(), orderedFields.size() + equalsFilterFields.size()); + if (endIndex == equalsFilterFields.size()) { + //the index is used only for filtering + return false; + } + //check that after that prefix there all the Order By fields in the right order + for (int i = equalsFilterFields.size(); i < endIndex; i++) { + int fieldOrderInOrderByClause = i - equalsFilterFields.size(); + final OPair pair = orderedFields.get(fieldOrderInOrderByClause); + + if (!firstOrder.equals(pair.getValue())) + return false; + + final String orderFieldName = pair.getKey().toLowerCase(Locale.ENGLISH); + final String indexFieldName = indexFields.get(i).toLowerCase(Locale.ENGLISH); + + if (!orderFieldName.equals(indexFieldName)) + return false; + } + + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OOriginalRecordsReturnHandler.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OOriginalRecordsReturnHandler.java new file mode 100644 index 00000000000..746910d94e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OOriginalRecordsReturnHandler.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OOriginalRecordsReturnHandler extends ORecordsReturnHandler { + public OOriginalRecordsReturnHandler(Object returnExpression, OCommandContext context) { + super(returnExpression, context); + } + + @Override + protected ODocument preprocess(ODocument result) { + return result.copy(); + } + + @Override + public void beforeUpdate(ODocument result) { + storeResult(result); + } + + @Override + public void afterUpdate(ODocument result) { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordCountHandler.java b/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordCountHandler.java new file mode 100644 index 00000000000..f83cceb0470 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordCountHandler.java @@ -0,0 +1,51 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * + * + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class ORecordCountHandler implements OReturnHandler { + private int count = 0; + + @Override + public void reset() { + count = 0; + } + + @Override + public void beforeUpdate(ODocument result) { + } + + @Override + public void afterUpdate(ODocument result) { + count++; + } + + @Override + public Object ret() { + return count; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordsReturnHandler.java b/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordsReturnHandler.java new file mode 100644 index 00000000000..937487c0f79 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/ORecordsReturnHandler.java @@ -0,0 +1,77 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public abstract class ORecordsReturnHandler implements OReturnHandler { + private final Object returnExpression; + private final OCommandContext context; + private List results; + + protected ORecordsReturnHandler(final Object returnExpression, final OCommandContext context) { + this.returnExpression = returnExpression; + this.context = context; + } + + @Override + public void reset() { + results = new ArrayList(); + } + + @Override + public Object ret() { + return results; + } + + protected void storeResult(final ODocument result) { + final ODocument processedResult = preprocess(result); + + results.add(evaluateExpression(processedResult)); + } + + protected abstract ODocument preprocess(final ODocument result); + + private Object evaluateExpression(final ODocument record) { + if (returnExpression == null) { + return record; + } else { + final Object itemResult; + final ODocument wrappingDoc; + context.setVariable("current", record); + + itemResult = OSQLHelper.getValue(returnExpression, (ODocument) ((OIdentifiable) record).getRecord(), context); + if (itemResult instanceof OIdentifiable) + return itemResult; + + // WRAP WITH ODOCUMENT TO BE TRANSFERRED THROUGH BINARY DRIVER + return new ODocument("value", itemResult); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OReturnHandler.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OReturnHandler.java new file mode 100644 index 00000000000..8ca2aee0b56 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OReturnHandler.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public interface OReturnHandler { + void reset(); + + void beforeUpdate(ODocument result); + + void afterUpdate(ODocument result); + + /** + * + * @return collected result + */ + Object ret(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/ORuntimeResult.java b/core/src/main/java/com/orientechnologies/orient/core/sql/ORuntimeResult.java new file mode 100755 index 00000000000..fa2f99abbbc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/ORuntimeResult.java @@ -0,0 +1,283 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.common.util.OResettable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.ORecordInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ORecordBytes; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemAbstract; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.method.OSQLMethodRuntime; +import com.orientechnologies.orient.core.sql.method.misc.OSQLMethodField; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Handles runtime results. + * + * @author Luca Garulli + */ +public class ORuntimeResult { + private final Object fieldValue; + private final Map projections; + private final ODocument value; + private OCommandContext context; + + public ORuntimeResult(final Object iFieldValue, final Map iProjections, final int iProgressive, + final OCommandContext iContext) { + fieldValue = iFieldValue; + projections = iProjections; + context = iContext; + value = createProjectionDocument(iProgressive); + } + + public static ODocument createProjectionDocument(final int iProgressive) { + final ODocument doc = new ODocument().setOrdered(true).setTrackingChanges(false); + // ASSIGN A TEMPORARY RID TO ALLOW PAGINATION IF ANY + ((ORecordId) doc.getIdentity()).setClusterId(-2); + ((ORecordId) doc.getIdentity()).setClusterPosition(iProgressive); + return doc; + } + + @SuppressWarnings("unchecked") public static ODocument applyRecord(final ODocument iValue, final Map iProjections, + final OCommandContext iContext, final OIdentifiable iRecord) { + // APPLY PROJECTIONS + + ORecord record = (iRecord != null ? iRecord.getRecord() : null); + //MANAGE SPECIFIC CASES FOR RECORD BYTES + if (ORecordBytes.RECORD_TYPE == ORecordInternal.getRecordType(record)) { + + for (Entry projection : iProjections.entrySet()) { + if ("@rid".equalsIgnoreCase("" + projection.getValue())) { + iValue.field(projection.getKey(), record.getIdentity()); + } else if ("@size".equalsIgnoreCase("" + projection.getValue())) { + iValue.field(projection.getKey(), record.getSize()); + } else if ("@version".equalsIgnoreCase("" + projection.getValue())) { + iValue.field(projection.getKey(), record.getVersion()); + } else { + Object val = projection.getValue(); + if (val instanceof Number || val instanceof String || val instanceof Boolean) { + iValue.field(projection.getKey(), val); + } else { + iValue.field(projection.getKey(), (Object) null); + } + } + } + return iValue; + } + final ODocument inputDocument = (ODocument) record; + + if (iProjections.isEmpty()) + // SELECT * CASE + inputDocument.copyTo(iValue); + else { + + for (Entry projection : iProjections.entrySet()) { + final String prjName = projection.getKey(); + + final Object v = projection.getValue(); + + if (v == null && prjName != null) { + iValue.field(prjName, (Object) null); + continue; + } + + final Object projectionValue; + if (v != null && v.equals("*")) { + // COPY ALL + inputDocument.copyTo(iValue); + // CONTINUE WITH NEXT ITEM + continue; + + } else if (v instanceof OSQLFilterItemVariable || v instanceof OSQLFilterItemField) { + final OSQLFilterItemAbstract var = (OSQLFilterItemAbstract) v; + final OPair last = var.getLastChainOperator(); + if (last != null && last.getKey().getMethod() instanceof OSQLMethodField && last.getValue() != null + && last.getValue().length == 1 && last.getValue()[0].equals("*")) { + final Object value = ((OSQLFilterItemAbstract) v).getValue(inputDocument, iValue, iContext); + if (inputDocument != null && value != null && inputDocument instanceof ODocument && value instanceof ODocument) { + // COPY FIELDS WITH PROJECTION NAME AS PREFIX + for (String fieldName : ((ODocument) value).fieldNames()) { + iValue.field(prjName + fieldName, ((ODocument) value).field(fieldName)); + } + } + projectionValue = null; + } else + // RETURN A VARIABLE FROM THE CONTEXT + projectionValue = ((OSQLFilterItemAbstract) v).getValue(inputDocument, iValue, iContext); + + } else if (v instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) v; + projectionValue = f.execute(inputDocument, inputDocument, iValue, iContext); + } else { + if (v == null) { + // SIMPLE NULL VALUE: SET IT IN DOCUMENT + iValue.field(prjName, v); + continue; + } + projectionValue = v; + } + + if (projectionValue != null) + if (projectionValue instanceof ORidBag) + iValue.field(prjName, new ORidBag((ORidBag) projectionValue)); + else if (projectionValue instanceof OIdentifiable && !(projectionValue instanceof ORID) + && !(projectionValue instanceof ORecord)) + iValue.field(prjName, ((OIdentifiable) projectionValue).getRecord()); + else if (projectionValue instanceof Iterator) { + boolean link = true; + // make temporary value typical case graph database elemenet's iterator edges + if (projectionValue instanceof OResettable) + ((OResettable) projectionValue).reset(); + + final List iteratorValues = new ArrayList(); + final Iterator projectionValueIterator = (Iterator) projectionValue; + while (projectionValueIterator.hasNext()) { + Object value = projectionValueIterator.next(); + if (value instanceof OIdentifiable) { + value = ((OIdentifiable) value).getRecord(); + if (value != null && !((OIdentifiable) value).getIdentity().isPersistent()) + link = false; + } + + if (value != null) + iteratorValues.add(value); + } + + iValue.field(prjName, iteratorValues, link ? OType.LINKLIST : OType.EMBEDDEDLIST); + } else if (projectionValue instanceof ODocument && ((ODocument) projectionValue).getIdentity().getClusterId() < 0) { + iValue.field(prjName, projectionValue, OType.EMBEDDED); + } else if (projectionValue instanceof Set) { + OType type = OType.getTypeByValue(projectionValue); + if (type == OType.LINKSET && !entriesPersistent((Collection) projectionValue)) + type = OType.EMBEDDEDSET; + iValue.field(prjName, projectionValue, type); + } else if (projectionValue instanceof Map) { + OType type = OType.getTypeByValue(projectionValue); + if (type == OType.LINKMAP && !entriesPersistent(((Map) projectionValue).values())) + type = OType.EMBEDDEDMAP; + iValue.field(prjName, projectionValue, type); + } else if (projectionValue instanceof List) { + OType type = OType.getTypeByValue(projectionValue); + if (type == OType.LINKLIST && !entriesPersistent((Collection) projectionValue)) + type = OType.EMBEDDEDLIST; + iValue.field(prjName, projectionValue, type); + + } else + iValue.field(prjName, projectionValue); + } + } + + return iValue; + } + + private static boolean entriesPersistent(Collection projectionValue) { + if (projectionValue instanceof ORecordLazyMultiValue) { + Iterator it = ((ORecordLazyMultiValue) projectionValue).rawIterator(); + while (it.hasNext()) { + OIdentifiable rec = it.next(); + if (rec != null && !rec.getIdentity().isPersistent()) + return false; + } + } else { + for (OIdentifiable rec : projectionValue) { + if (rec != null && !rec.getIdentity().isPersistent()) + return false; + } + } + return true; + } + + public static ODocument getResult(final ODocument iValue, final Map iProjections) { + if (iValue != null) { + + boolean canExcludeResult = false; + + for (Entry projection : iProjections.entrySet()) { + if (!iValue.containsField(projection.getKey())) { + // ONLY IF NOT ALREADY CONTAINS A VALUE, OTHERWISE HAS BEEN SET MANUALLY (INDEX?) + final Object v = projection.getValue(); + if (v instanceof OSQLFunctionRuntime) { + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) v; + canExcludeResult = f.filterResult(); + + Object fieldValue = f.getResult(); + + if (fieldValue != null) + iValue.field(projection.getKey(), fieldValue); + } + } + } + + if (canExcludeResult && iValue.isEmpty()) + // RESULT EXCLUDED FOR EMPTY RECORD + return null; + + // AVOID SAVING OF TEMP RECORD + ORecordInternal.unsetDirty(iValue); + } + return iValue; + } + + public static ODocument getProjectionResult(final int iId, final Map iProjections, final OCommandContext iContext, + final OIdentifiable iRecord) { + return ORuntimeResult + .getResult(ORuntimeResult.applyRecord(ORuntimeResult.createProjectionDocument(iId), iProjections, iContext, iRecord), + iProjections); + } + + public ODocument applyRecord(final OIdentifiable iRecord) { + // SYNCHRONIZE ACCESS TO AVOID CONTENTION ON THE SAME INSTANCE + synchronized (this) { + return applyRecord(value, projections, context, iRecord); + } + } + + /** + * Set a single value. This is useful in case of query optimization like with indexes + * + * @param iName Field name + * @param iValue Field value + */ + public void applyValue(final String iName, final Object iValue) { + value.field(iName, iValue); + } + + public ODocument getResult() { + return getResult(value, projections); + } + + public Object getFieldValue() { + return fieldValue; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLEngine.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLEngine.java new file mode 100755 index 00000000000..32464654201 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLEngine.java @@ -0,0 +1,510 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.common.util.OCollections; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.collate.OCollateFactory; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandExecutor; +import com.orientechnologies.orient.core.command.OCommandExecutorAbstract; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilter; +import com.orientechnologies.orient.core.sql.filter.OSQLTarget; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionFactory; +import com.orientechnologies.orient.core.sql.method.OSQLMethod; +import com.orientechnologies.orient.core.sql.method.OSQLMethodFactory; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorFactory; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.*; + +import static com.orientechnologies.common.util.OClassLoaderHelper.lookupProviderWithOrientClassLoader; + +public class OSQLEngine { + + protected static final OSQLEngine INSTANCE = new OSQLEngine(); + private static volatile List FUNCTION_FACTORIES = null; + private static List METHOD_FACTORIES = null; + private static List EXECUTOR_FACTORIES = null; + private static List OPERATOR_FACTORIES = null; + private static List COLLATE_FACTORIES = null; + private static OQueryOperator[] SORTED_OPERATORS = null; + private static ClassLoader orientClassLoader = OSQLEngine.class.getClassLoader(); + + /** + * internal use only, to sort operators. + */ + private static final class Pair { + + final OQueryOperator before; + final OQueryOperator after; + + public Pair(final OQueryOperator before, final OQueryOperator after) { + this.before = before; + this.after = after; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof Pair) { + final Pair that = (Pair) obj; + return before == that.before && after == that.after; + } + return false; + } + + @Override + public int hashCode() { + return System.identityHashCode(before) + 31 * System.identityHashCode(after); + } + + @Override + public String toString() { + return before + " > " + after; + } + + } + + protected OSQLEngine() { + } + + public static void registerOperator(final OQueryOperator iOperator) { + ODynamicSQLElementFactory.OPERATORS.add(iOperator); + SORTED_OPERATORS = null; // clear cache + } + + /** + * @return Iterator of all function factories + */ + public static Iterator getFunctionFactories() { + if (FUNCTION_FACTORIES == null) { + synchronized (INSTANCE) { + if (FUNCTION_FACTORIES == null) { + final Iterator ite = lookupProviderWithOrientClassLoader(OSQLFunctionFactory.class, + orientClassLoader); + + final List factories = new ArrayList(); + while (ite.hasNext()) { + factories.add(ite.next()); + } + FUNCTION_FACTORIES = Collections.unmodifiableList(factories); + } + } + } + return FUNCTION_FACTORIES.iterator(); + } + + public static Iterator getMethodFactories() { + if (METHOD_FACTORIES == null) { + synchronized (INSTANCE) { + if (METHOD_FACTORIES == null) { + final Iterator ite = lookupProviderWithOrientClassLoader(OSQLMethodFactory.class, orientClassLoader); + + final List factories = new ArrayList(); + while (ite.hasNext()) { + factories.add(ite.next()); + } + METHOD_FACTORIES = Collections.unmodifiableList(factories); + } + } + } + return METHOD_FACTORIES.iterator(); + } + + /** + * @return Iterator of all function factories + */ + public static Iterator getCollateFactories() { + if (COLLATE_FACTORIES == null) { + synchronized (INSTANCE) { + if (COLLATE_FACTORIES == null) { + + final Iterator ite = lookupProviderWithOrientClassLoader(OCollateFactory.class, orientClassLoader); + + final List factories = new ArrayList(); + while (ite.hasNext()) { + factories.add(ite.next()); + } + COLLATE_FACTORIES = Collections.unmodifiableList(factories); + } + } + } + return COLLATE_FACTORIES.iterator(); + } + + /** + * @return Iterator of all operator factories + */ + public static Iterator getOperatorFactories() { + if (OPERATOR_FACTORIES == null) { + synchronized (INSTANCE) { + if (OPERATOR_FACTORIES == null) { + final Iterator ite = lookupProviderWithOrientClassLoader(OQueryOperatorFactory.class, + orientClassLoader); + + final List factories = new ArrayList(); + while (ite.hasNext()) { + factories.add(ite.next()); + } + OPERATOR_FACTORIES = Collections.unmodifiableList(factories); + } + } + } + return OPERATOR_FACTORIES.iterator(); + } + + /** + * @return Iterator of all command factories + */ + public static Iterator getCommandFactories() { + if (EXECUTOR_FACTORIES == null) { + synchronized (INSTANCE) { + if (EXECUTOR_FACTORIES == null) { + final Iterator ite = lookupProviderWithOrientClassLoader(OCommandExecutorSQLFactory.class, + orientClassLoader); + final List factories = new ArrayList(); + while (ite.hasNext()) { + try { + factories.add(ite.next()); + } catch (Exception e) { + OLogManager.instance().warn(null, "Cannot load OCommandExecutorSQLFactory instance from service registry", e); + } + } + EXECUTOR_FACTORIES = Collections.unmodifiableList(factories); + } + } + } + return EXECUTOR_FACTORIES.iterator(); + } + + /** + * Iterates on all factories and append all function names. + * + * @return Set of all function names. + */ + public static Set getFunctionNames() { + final Set types = new HashSet(); + final Iterator ite = getFunctionFactories(); + while (ite.hasNext()) { + types.addAll(ite.next().getFunctionNames()); + } + return types; + } + + public static Set getMethodNames() { + final Set types = new HashSet(); + final Iterator ite = getMethodFactories(); + while (ite.hasNext()) { + types.addAll(ite.next().getMethodNames()); + } + return types; + } + + /** + * Iterates on all factories and append all collate names. + * + * @return Set of all colate names. + */ + public static Set getCollateNames() { + final Set types = new HashSet(); + final Iterator ite = getCollateFactories(); + while (ite.hasNext()) { + types.addAll(ite.next().getNames()); + } + return types; + } + + /** + * Iterates on all factories and append all command names. + * + * @return Set of all command names. + */ + public static Set getCommandNames() { + final Set types = new HashSet(); + final Iterator ite = getCommandFactories(); + while (ite.hasNext()) { + types.addAll(ite.next().getCommandNames()); + } + return types; + } + + /** + * Scans for factory plug-ins on the application class path. This method is needed because the application class path can + * theoretically change, or additional plug-ins may become available. Rather than re-scanning the classpath on every invocation of + * the API, the class path is scanned automatically only on the first invocation. Clients can call this method to prompt a + * re-scan. Thus this method need only be invoked by sophisticated applications which dynamically make new plug-ins available at + * runtime. + */ + public static void scanForPlugins() { + // clear cache, will cause a rescan on next getFunctionFactories call + FUNCTION_FACTORIES = null; + } + + public static Object foreachRecord(final OCallable iCallable, final Object iCurrent, + final OCommandContext iContext) { + if (iCurrent == null) + return null; + + if (!OCommandExecutorAbstract.checkInterruption(iContext)) + return null; + + if (OMultiValue.isMultiValue(iCurrent) || iCurrent instanceof Iterator) { + final OMultiCollectionIterator result = new OMultiCollectionIterator(); + for (Object o : OMultiValue.getMultiValueIterable(iCurrent, false)) { + if (iContext != null && !iContext.checkTimeout()) + return null; + + if (OMultiValue.isMultiValue(o) || o instanceof Iterator) { + for (Object inner : OMultiValue.getMultiValueIterable(o, false)) { + result.add(iCallable.call((OIdentifiable) inner)); + } + } else + result.add(iCallable.call((OIdentifiable) o)); + } + return result; + } else if (iCurrent instanceof OIdentifiable) + return iCallable.call((OIdentifiable) iCurrent); + + return null; + } + + public static OSQLEngine getInstance() { + return INSTANCE; + } + + public static OCollate getCollate(final String name) { + for (Iterator iter = getCollateFactories(); iter.hasNext(); ) { + OCollateFactory f = iter.next(); + final OCollate c = f.getCollate(name); + if (c != null) + return c; + } + return null; + } + + public static OSQLMethod getMethod(String iMethodName) { + iMethodName = iMethodName.toLowerCase(Locale.ENGLISH); + + final Iterator ite = getMethodFactories(); + while (ite.hasNext()) { + final OSQLMethodFactory factory = ite.next(); + if (factory.hasMethod(iMethodName)) { + return factory.createMethod(iMethodName); + } + } + + return null; + } + + public OQueryOperator[] getRecordOperators() { + if (SORTED_OPERATORS == null) { + synchronized (INSTANCE) { + if (SORTED_OPERATORS == null) { + + // sort operators, will happen only very few times since we cache the + // result + final Iterator ite = getOperatorFactories(); + final List operators = new ArrayList(); + while (ite.hasNext()) { + final OQueryOperatorFactory factory = ite.next(); + operators.addAll(factory.getOperators()); + } + + final List sorted = new ArrayList(); + final Set pairs = new LinkedHashSet(); + for (final OQueryOperator ca : operators) { + for (final OQueryOperator cb : operators) { + if (ca != cb) { + switch (ca.compare(cb)) { + case BEFORE: + pairs.add(new Pair(ca, cb)); + break; + case AFTER: + pairs.add(new Pair(cb, ca)); + break; + } + switch (cb.compare(ca)) { + case BEFORE: + pairs.add(new Pair(cb, ca)); + break; + case AFTER: + pairs.add(new Pair(ca, cb)); + break; + } + } + } + } + boolean added; + do { + added = false; + scan: + for (final Iterator it = operators.iterator(); it.hasNext(); ) { + final OQueryOperator candidate = it.next(); + for (final Pair pair : pairs) { + if (pair.after == candidate) { + continue scan; + } + } + sorted.add(candidate); + it.remove(); + for (final Iterator itp = pairs.iterator(); itp.hasNext(); ) { + if (itp.next().before == candidate) { + itp.remove(); + } + } + added = true; + } + } while (added); + if (!operators.isEmpty()) { + throw new ODatabaseException("Invalid sorting. " + OCollections.toString(pairs)); + } + SORTED_OPERATORS = sorted.toArray(new OQueryOperator[sorted.size()]); + } + } + } + return SORTED_OPERATORS; + } + + public void registerFunction(final String iName, final OSQLFunction iFunction) { + ODynamicSQLElementFactory.FUNCTIONS.put(iName.toLowerCase(Locale.ENGLISH), iFunction); + } + + public void registerFunction(final String iName, final Class iFunctionClass) { + ODynamicSQLElementFactory.FUNCTIONS.put(iName.toLowerCase(Locale.ENGLISH), iFunctionClass); + } + + public OSQLFunction getFunction(String iFunctionName) { + iFunctionName = iFunctionName.toLowerCase(Locale.ENGLISH); + + if (iFunctionName.equalsIgnoreCase("any") || iFunctionName.equalsIgnoreCase("all")) + // SPECIAL FUNCTIONS + return null; + + final Iterator ite = getFunctionFactories(); + while (ite.hasNext()) { + final OSQLFunctionFactory factory = ite.next(); + if (factory.hasFunction(iFunctionName)) { + return factory.createFunction(iFunctionName); + } + } + + throw new OCommandSQLParsingException( + "No function with name '" + iFunctionName + "', available names are : " + OCollections.toString(getFunctionNames())); + } + + public void unregisterFunction(String iName) { + iName = iName.toLowerCase(Locale.ENGLISH); + ODynamicSQLElementFactory.FUNCTIONS.remove(iName); + } + + public OCommandExecutor getCommand(String candidate) { + candidate = candidate.trim(); + final Set names = getCommandNames(); + String commandName = candidate; + boolean found = names.contains(commandName); + int pos = -1; + while (!found) { + pos = OStringSerializerHelper.getLowerIndexOf(candidate, pos + 1, " ", "\n", "\r", "\t", "(", "["); + if (pos > -1) { + commandName = candidate.substring(0, pos); + found = names.contains(commandName); + } else { + break; + } + } + + if (found) { + final Iterator ite = getCommandFactories(); + while (ite.hasNext()) { + final OCommandExecutorSQLFactory factory = ite.next(); + if (factory.getCommandNames().contains(commandName)) { + return factory.createCommand(commandName); + } + } + } + + return null; + } + + public OSQLFilter parseCondition(final String iText, final OCommandContext iContext, final String iFilterKeyword) { + return new OSQLFilter(iText, iContext, iFilterKeyword); + } + + public OSQLTarget parseTarget(final String iText, final OCommandContext iContext) { + return new OSQLTarget(iText, iContext); + } + + public Set parseRIDTarget(final ODatabaseDocument database, String iTarget, final OCommandContext iContext, + Map iArgs) { + final Set ids; + if (iTarget.startsWith("(")) { + // SUB-QUERY + final OSQLSynchQuery query = new OSQLSynchQuery(iTarget.substring(1, iTarget.length() - 1)); + query.setContext(iContext); + + final List result = database.query(query, iArgs); + if (result == null || result.isEmpty()) + ids = Collections.emptySet(); + else { + ids = new HashSet((int) (result.size() * 1.3)); + for (OIdentifiable aResult : result) + ids.add(aResult.getIdentity()); + } + } else if (iTarget.startsWith("[")) { + // COLLECTION OF RIDS + final String[] idsAsStrings = iTarget.substring(1, iTarget.length() - 1).split(","); + ids = new HashSet((int) (idsAsStrings.length * 1.3)); + for (String idsAsString : idsAsStrings) { + if (idsAsString.startsWith("$")) { + Object r = iContext.getVariable(idsAsString); + if (r instanceof OIdentifiable) + ids.add((OIdentifiable) r); + else + OMultiValue.add(ids, r); + } else + ids.add(new ORecordId(idsAsString)); + } + } else { + // SINGLE RID + if (iTarget.startsWith("$")) { + Object r = iContext.getVariable(iTarget); + if (r instanceof OIdentifiable) + ids = Collections.singleton((OIdentifiable) r); + else + ids = (Set) OMultiValue.add(new HashSet(OMultiValue.getSize(r)), r); + + } else + ids = Collections.singleton(new ORecordId(iTarget)); + + } + return ids; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLHelper.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLHelper.java new file mode 100755 index 00000000000..51e995de12b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLHelper.java @@ -0,0 +1,400 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerCSVAbstract; +import com.orientechnologies.orient.core.sql.filter.*; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; + +import java.math.BigDecimal; +import java.util.*; + +/** + * SQL Helper class + * + * @author Luca Garulli + */ +public class OSQLHelper { + public static final String NAME = "sql"; + + public static final String VALUE_NOT_PARSED = "_NOT_PARSED_"; + public static final String NOT_NULL = "_NOT_NULL_"; + public static final String DEFINED = "_DEFINED_"; + + private static ClassLoader orientClassLoader = OSQLFilterItemAbstract.class.getClassLoader(); + + public static Object parseDefaultValue(ODocument iRecord, final String iWord) { + final Object v = OSQLHelper.parseValue(iWord, null); + + if (v != VALUE_NOT_PARSED) { + return v; + } + + // TRY TO PARSE AS FUNCTION + final OSQLFunctionRuntime func = OSQLHelper.getFunction(null, iWord); + if (func != null) { + return func.execute(iRecord, iRecord, null, null); + } + + // PARSE AS FIELD + return iWord; + } + + /** + * Convert fields from text to real value. Supports: String, RID, Boolean, Float, Integer and NULL. + * + * @param iValue Value to convert. + * + * @return The value converted if recognized, otherwise VALUE_NOT_PARSED + */ + public static Object parseValue(String iValue, final OCommandContext iContext) { + return parseValue(iValue, iContext, false); + } + + public static Object parseValue(String iValue, final OCommandContext iContext, boolean resolveContextVariables) { + + if (iValue == null) + return null; + + iValue = iValue.trim(); + + Object fieldValue = VALUE_NOT_PARSED; + + if (iValue.startsWith("'") && iValue.endsWith("'") || iValue.startsWith("\"") && iValue.endsWith("\"")) + // STRING + fieldValue = OStringSerializerHelper.decode(OIOUtils.getStringContent(iValue)); + else if (iValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.LIST_END) { + // COLLECTION/ARRAY + final List items = OStringSerializerHelper + .smartSplit(iValue.substring(1, iValue.length() - 1), OStringSerializerHelper.RECORD_SEPARATOR); + + final List coll = new ArrayList(); + for (String item : items) { + coll.add(parseValue(item, iContext, resolveContextVariables)); + } + fieldValue = coll; + + } else if (iValue.charAt(0) == OStringSerializerHelper.MAP_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.MAP_END) { + // MAP + final List items = OStringSerializerHelper + .smartSplit(iValue.substring(1, iValue.length() - 1), OStringSerializerHelper.RECORD_SEPARATOR); + + final Map map = new HashMap(); + for (String item : items) { + final List parts = OStringSerializerHelper.smartSplit(item, OStringSerializerHelper.ENTRY_SEPARATOR); + + if (parts == null || parts.size() != 2) + throw new OCommandSQLParsingException("Map found but entries are not defined as :"); + + Object key = OStringSerializerHelper.decode(parseValue(parts.get(0), iContext).toString()); + Object value = parseValue(parts.get(1), iContext); + if (VALUE_NOT_PARSED == value) { + value = new OSQLPredicate(parts.get(1)).evaluate(iContext); + } + map.put(key, value); + } + + if (map.containsKey(ODocumentHelper.ATTRIBUTE_TYPE)) + // IT'S A DOCUMENT + // TODO: IMPROVE THIS CASE AVOIDING DOUBLE PARSING + fieldValue = new ODocument().fromJSON(iValue); + else + fieldValue = map; + + } else if (iValue.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN + && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.EMBEDDED_END) { + // SUB-COMMAND + fieldValue = new OCommandSQL(iValue.substring(1, iValue.length() - 1)); + ((OCommandSQL) fieldValue).getContext().setParent(iContext); + + } else if (ORecordId.isA(iValue)) + // RID + fieldValue = new ORecordId(iValue.trim()); + else { + + if (iValue.equalsIgnoreCase("null")) + // NULL + fieldValue = null; + else if (iValue.equalsIgnoreCase("not null")) + // NULL + fieldValue = NOT_NULL; + else if (iValue.equalsIgnoreCase("defined")) + // NULL + fieldValue = DEFINED; + else if (iValue.equalsIgnoreCase("true")) + // BOOLEAN, TRUE + fieldValue = Boolean.TRUE; + else if (iValue.equalsIgnoreCase("false")) + // BOOLEAN, FALSE + fieldValue = Boolean.FALSE; + else if (iValue.startsWith("date(")) { + final OSQLFunctionRuntime func = OSQLHelper.getFunction(null, iValue); + if (func != null) { + fieldValue = func.execute(null, null, null, iContext); + } + } else if (resolveContextVariables && iValue.startsWith("$") && iContext != null) { + fieldValue = iContext.getVariable(iValue); + } else { + final Object v = parseStringNumber(iValue); + if (v != null) + fieldValue = v; + } + } + + return fieldValue; + } + + public static Object parseStringNumber(final String iValue) { + final OType t = ORecordSerializerCSVAbstract.getType(iValue); + + if (t == OType.INTEGER) + return Integer.parseInt(iValue); + else if (t == OType.LONG) + return Long.parseLong(iValue); + else if (t == OType.FLOAT) + return Float.parseFloat(iValue); + else if (t == OType.SHORT) + return Short.parseShort(iValue); + else if (t == OType.BYTE) + return Byte.parseByte(iValue); + else if (t == OType.DOUBLE) + return Double.parseDouble(iValue); + else if (t == OType.DECIMAL) + return new BigDecimal(iValue); + else if (t == OType.DATE || t == OType.DATETIME) + return new Date(Long.parseLong(iValue)); + + return null; + } + + public static Object parseValue(final OSQLPredicate iSQLFilter, final OBaseParser iCommand, final String iWord, + final OCommandContext iContext) { + if (iWord.charAt(0) == OStringSerializerHelper.PARAMETER_POSITIONAL + || iWord.charAt(0) == OStringSerializerHelper.PARAMETER_NAMED) { + if (iSQLFilter != null) + return iSQLFilter.addParameter(iWord); + else + return new OSQLFilterItemParameter(iWord); + } else + return parseValue(iCommand, iWord, iContext); + } + + public static Object parseValue(final OBaseParser iCommand, final String iWord, final OCommandContext iContext) { + return parseValue(iCommand, iWord, iContext, false); + } + public static Object parseValue(final OBaseParser iCommand, final String iWord, final OCommandContext iContext, boolean resolveContextVariables) { + if (iWord.equals("*")) + return "*"; + + // TRY TO PARSE AS RAW VALUE + final Object v = parseValue(iWord, iContext, resolveContextVariables); + if (v != VALUE_NOT_PARSED) + return v; + + if (!iWord.equalsIgnoreCase("any()") && !iWord.equalsIgnoreCase("all()")) { + // TRY TO PARSE AS FUNCTION + final Object func = OSQLHelper.getFunction(iCommand, iWord); + if (func != null) + return func; + } + + if (iWord.startsWith("$")) + // CONTEXT VARIABLE + return new OSQLFilterItemVariable(iCommand, iWord); + + // PARSE AS FIELD + return new OSQLFilterItemField(iCommand, iWord, null); + } + + public static OSQLFunctionRuntime getFunction(final OBaseParser iCommand, final String iWord) { + final int separator = iWord.indexOf('.'); + final int beginParenthesis = iWord.indexOf(OStringSerializerHelper.EMBEDDED_BEGIN); + if (beginParenthesis > -1 && (separator == -1 || separator > beginParenthesis)) { + final int endParenthesis = iWord.indexOf(OStringSerializerHelper.EMBEDDED_END, beginParenthesis); + + final char firstChar = iWord.charAt(0); + if (endParenthesis > -1 && (firstChar == '_' || Character.isLetter(firstChar))) + // FUNCTION: CREATE A RUN-TIME CONTAINER FOR IT TO SAVE THE PARAMETERS + return new OSQLFunctionRuntime(iCommand, iWord); + } + + return null; + } + + public static Object getValue(final Object iObject) { + if (iObject == null) + return null; + + if (iObject instanceof OSQLFilterItem) + return ((OSQLFilterItem) iObject).getValue(null, null, null); + + return iObject; + } + + public static Object getValue(final Object iObject, final ORecord iRecord, final OCommandContext iContext) { + if (iObject == null) + return null; + + if (iObject instanceof OSQLFilterItem) + return ((OSQLFilterItem) iObject).getValue(iRecord, null, iContext); + else if (iObject instanceof String) { + final String s = ((String) iObject).trim(); + if (iRecord != null & !s.isEmpty() && !OIOUtils.isStringContent(iObject) && !Character.isDigit(s.charAt(0))) + // INTERPRETS IT + return ODocumentHelper.getFieldValue(iRecord, s, iContext); + } + + return iObject; + } + + public static Object resolveFieldValue(final ODocument iDocument, final String iFieldName, final Object iFieldValue, + final OCommandParameters iArguments, final OCommandContext iContext) { + if (iFieldValue instanceof OSQLFilterItemField) { + final OSQLFilterItemField f = (OSQLFilterItemField) iFieldValue; + if (f.getRoot().equals("?")) + // POSITIONAL PARAMETER + return iArguments.getNext(); + else if (f.getRoot().startsWith(":")) + // NAMED PARAMETER + return iArguments.getByName(f.getRoot().substring(1)); + } + + if (iFieldValue instanceof ODocument && !((ODocument) iFieldValue).getIdentity().isValid()) + // EMBEDDED DOCUMENT + ODocumentInternal.addOwner((ODocument) iFieldValue, iDocument); + + // can't use existing getValue with iContext + if (iFieldValue == null) + return null; + if (iFieldValue instanceof OSQLFilterItem) + return ((OSQLFilterItem) iFieldValue).getValue(iDocument, null, iContext); + + return iFieldValue; + } + + public static ODocument bindParameters(final ODocument iDocument, final Map iFields, + final OCommandParameters iArguments, final OCommandContext iContext) { + if (iFields == null) + return null; + + final List> fields = new ArrayList>(iFields.size()); + + for (Map.Entry entry : iFields.entrySet()) + fields.add(new OPair(entry.getKey(), entry.getValue())); + + return bindParameters(iDocument, fields, iArguments, iContext); + } + + public static ODocument bindParameters(final ODocument iDocument, final List> iFields, + final OCommandParameters iArguments, final OCommandContext iContext) { + if (iFields == null) + return null; + + // BIND VALUES + for (OPair field : iFields) { + final String fieldName = field.getKey(); + Object fieldValue = field.getValue(); + + if (fieldValue != null) { + if (fieldValue instanceof OCommandSQL) { + final OCommandSQL cmd = (OCommandSQL) fieldValue; + cmd.getContext().setParent(iContext); + fieldValue = ODatabaseRecordThreadLocal.INSTANCE.get().command(cmd).execute(); + + // CHECK FOR CONVERSIONS + OImmutableClass immutableClass = ODocumentInternal.getImmutableSchemaClass(iDocument); + if (immutableClass != null) { + final OProperty prop = immutableClass.getProperty(fieldName); + if (prop != null) { + if (prop.getType() == OType.LINK) { + if (OMultiValue.isMultiValue(fieldValue)) { + final int size = OMultiValue.getSize(fieldValue); + if (size == 1) + // GET THE FIRST ITEM AS UNIQUE LINK + fieldValue = OMultiValue.getFirstValue(fieldValue); + else if (size == 0) + // NO ITEMS, SET IT AS NULL + fieldValue = null; + } + } + } else if (immutableClass.isEdgeType() && ("out".equals(fieldName) || "in".equals(fieldName)) + && (fieldValue instanceof List)) { + List lst = (List) fieldValue; + if (lst.size() == 1) { + fieldValue = lst.get(0); + } + } + + } + + if (OMultiValue.isMultiValue(fieldValue)) { + final List tempColl = new ArrayList(OMultiValue.getSize(fieldValue)); + + String singleFieldName = null; + for (Object o : OMultiValue.getMultiValueIterable(fieldValue, false)) { + if (o instanceof OIdentifiable && !((OIdentifiable) o).getIdentity().isPersistent()) { + // TEMPORARY / EMBEDDED + final ORecord rec = ((OIdentifiable) o).getRecord(); + if (rec != null && rec instanceof ODocument) { + // CHECK FOR ONE FIELD ONLY + final ODocument doc = (ODocument) rec; + if (doc.fields() == 1) { + singleFieldName = doc.fieldNames()[0]; + tempColl.add(doc.field(singleFieldName)); + } else { + // TRANSFORM IT IN EMBEDDED + doc.getIdentity().reset(); + ODocumentInternal.addOwner(doc, iDocument); + ODocumentInternal.addOwner(doc, iDocument); + tempColl.add(doc); + } + } + } else + tempColl.add(o); + } + + fieldValue = tempColl; + } + } + } + + iDocument.field(fieldName, resolveFieldValue(iDocument, fieldName, fieldValue, iArguments, iContext)); + } + return iDocument; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngine.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngine.java new file mode 100644 index 00000000000..2709b942a5c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngine.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.script.OCommandScript; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import java.io.IOException; +import java.io.Reader; + +/** + * Dynamic script engine for OrientDB SQL commands. This implementation is multi-threads. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLScriptEngine implements ScriptEngine { + + public static final String NAME = "sql"; + private ScriptEngineFactory factory; + + public OSQLScriptEngine(ScriptEngineFactory factory) { + this.factory = factory; + } + + @Override + public Object eval(String script, ScriptContext context) throws ScriptException { + return eval(script, (Bindings) null); + } + + @Override + public Object eval(Reader reader, ScriptContext context) throws ScriptException { + return eval(reader, (Bindings) null); + } + + @Override + public Object eval(String script) throws ScriptException { + return eval(script, (Bindings) null); + } + + @Override + public Object eval(Reader reader) throws ScriptException { + return eval(reader, (Bindings) null); + } + + @Override + public Object eval(String script, Bindings n) throws ScriptException { + return new OCommandScript(script).execute(n); + } + + @Override + public Object eval(Reader reader, Bindings n) throws ScriptException { + final StringBuilder buffer = new StringBuilder(); + try { + while (reader.ready()) + buffer.append((char) reader.read()); + } catch (IOException e) { + throw new ScriptException(e); + } + + return new OCommandScript(buffer.toString()).execute(n); + } + + @Override + public void put(String key, Object value) { + } + + @Override + public Object get(String key) { + return null; + } + + @Override + public Bindings getBindings(int scope) { + return new SimpleBindings(); + } + + @Override + public void setBindings(Bindings bindings, int scope) { + } + + @Override + public Bindings createBindings() { + return new SimpleBindings(); + } + + @Override + public ScriptContext getContext() { + return null; + } + + @Override + public void setContext(ScriptContext context) { + } + + @Override + public ScriptEngineFactory getFactory() { + return factory; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngineFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngineFactory.java new file mode 100644 index 00000000000..8f7e73b1989 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OSQLScriptEngineFactory.java @@ -0,0 +1,109 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import java.util.ArrayList; +import java.util.List; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +import com.orientechnologies.orient.core.OConstants; + +/** + * Dynamic script engine factory for OrientDB SQL commands. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLScriptEngineFactory implements ScriptEngineFactory { + + private static final List NAMES = new ArrayList(); + private static final List EXTENSIONS = new ArrayList(); + + static { + NAMES.add(OSQLScriptEngine.NAME); + EXTENSIONS.add(OSQLScriptEngine.NAME); + } + + @Override + public String getEngineName() { + return OSQLScriptEngine.NAME; + } + + @Override + public String getEngineVersion() { + return OConstants.ORIENT_VERSION; + } + + @Override + public List getExtensions() { + return EXTENSIONS; + } + + @Override + public List getMimeTypes() { + return null; + } + + @Override + public List getNames() { + return NAMES; + } + + @Override + public String getLanguageName() { + return OSQLScriptEngine.NAME; + } + + @Override + public String getLanguageVersion() { + return OConstants.ORIENT_VERSION; + } + + @Override + public Object getParameter(String key) { + return null; + } + + @Override + public String getMethodCallSyntax(String obj, String m, String... args) { + return null; + } + + @Override + public String getOutputStatement(String toDisplay) { + return null; + } + + @Override + public String getProgram(String... statements) { + final StringBuilder buffer = new StringBuilder(); + for (String s : statements) + buffer.append(s).append(";\n"); + return buffer.toString(); + } + + @Override + public ScriptEngine getScriptEngine() { + return new OSQLScriptEngine(this); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OTemporaryRidGenerator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OTemporaryRidGenerator.java new file mode 100644 index 00000000000..e400dd9de75 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OTemporaryRidGenerator.java @@ -0,0 +1,9 @@ +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; + +public interface OTemporaryRidGenerator { + + int getTemporaryRIDCounter(final OCommandContext iContext); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/OUpdatedRecordsReturnHandler.java b/core/src/main/java/com/orientechnologies/orient/core/sql/OUpdatedRecordsReturnHandler.java new file mode 100644 index 00000000000..14578f0327b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/OUpdatedRecordsReturnHandler.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OUpdatedRecordsReturnHandler extends ORecordsReturnHandler { + public OUpdatedRecordsReturnHandler(Object returnExpression, OCommandContext context) { + super(returnExpression, context); + } + + @Override + protected ODocument preprocess(ODocument result) { + return result; + } + + @Override + public void beforeUpdate(ODocument result) { + + } + + @Override + public void afterUpdate(ODocument result) { + storeResult(result); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/SQLUpdateReturnModeEnum.java b/core/src/main/java/com/orientechnologies/orient/core/sql/SQLUpdateReturnModeEnum.java new file mode 100644 index 00000000000..f062347067b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/SQLUpdateReturnModeEnum.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql; +/** + * Created by Kowalot on 10.05.14. + */ +public enum SQLUpdateReturnModeEnum { + COUNT, + BEFORE, + AFTER +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OFilterOptimizer.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OFilterOptimizer.java new file mode 100644 index 00000000000..41c9f182da2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OFilterOptimizer.java @@ -0,0 +1,115 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.orient.core.sql.OIndexSearchResult; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals; + +import java.util.Map; + +/** + * @author Artem Orobets (enisher-at-gmail.com) + */ +public class OFilterOptimizer { + public void optimize(OSQLFilter filter, OIndexSearchResult indexMatch) { + filter.setRootCondition(optimize(filter.getRootCondition(), indexMatch)); + } + + private OSQLFilterCondition optimize(OSQLFilterCondition condition, OIndexSearchResult indexMatch) { + if (condition == null) { + return null; + } + OQueryOperator operator = condition.getOperator(); + while (operator == null) { + if (condition.getRight() == null && condition.getLeft() instanceof OSQLFilterCondition) { + condition = (OSQLFilterCondition) condition.getLeft(); + operator = condition.getOperator(); + } else { + return condition; + } + } + + final OIndexReuseType reuseType = operator.getIndexReuseType(condition.getLeft(), condition.getRight()); + switch (reuseType) { + case INDEX_METHOD: + if (isCovered(indexMatch, operator, condition.getLeft(), condition.getRight()) + || isCovered(indexMatch, operator, condition.getRight(), condition.getLeft())) { + return null; + } + return condition; + + case INDEX_INTERSECTION: + if (condition.getLeft() instanceof OSQLFilterCondition) + condition.setLeft(optimize((OSQLFilterCondition) condition.getLeft(), indexMatch)); + + if (condition.getRight() instanceof OSQLFilterCondition) + condition.setRight(optimize((OSQLFilterCondition) condition.getRight(), indexMatch)); + + if (condition.getLeft() == null) + return (OSQLFilterCondition) condition.getRight(); + if (condition.getRight() == null) + return (OSQLFilterCondition) condition.getLeft(); + return condition; + + case INDEX_OPERATOR: + if (isCovered(indexMatch, operator, condition.getLeft(), condition.getRight()) + || isCovered(indexMatch, operator, condition.getRight(), condition.getLeft())) { + return null; + } + return condition; + default: + return condition; + } + } + + private boolean isCovered(OIndexSearchResult indexMatch, OQueryOperator operator, Object fieldCandidate, Object valueCandidate) { + if (fieldCandidate instanceof OSQLFilterItemField) { + final OSQLFilterItemField field = (OSQLFilterItemField) fieldCandidate; + if (operator instanceof OQueryOperatorEquals) + for (Map.Entry e : indexMatch.fieldValuePairs.entrySet()) { + if (isSameField(field, e.getKey()) && isSameValue(valueCandidate, e.getValue())) + return true; + } + + return operator.equals(indexMatch.lastOperator) && isSameField(field, indexMatch.lastField) + && isSameValue(valueCandidate, indexMatch.lastValue); + } + return false; + } + + private boolean isSameValue(Object valueCandidate, Object lastValue) { + if (lastValue == null || valueCandidate == null) + return lastValue == null && valueCandidate == null; + + return lastValue.equals(valueCandidate) || lastValue.equals(OSQLHelper.getValue(valueCandidate)); + } + + private boolean isSameField(OSQLFilterItemField field, OSQLFilterItemField.FieldChain fieldChain) { + return fieldChain.belongsTo(field); + } + + private boolean isSameField(OSQLFilterItemField field, String fieldName) { + return !field.hasChainOperators() && fieldName.equals(field.name); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilter.java new file mode 100755 index 00000000000..7c19112b83b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilter.java @@ -0,0 +1,119 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandPredicate; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Locale; + +/** + * Parsed query. It's built once a query is parsed. + * + * @author Luca Garulli + */ +public class OSQLFilter extends OSQLPredicate implements OCommandPredicate { + public OSQLFilter(final String iText, final OCommandContext iContext, final String iFilterKeyword) { + super(); + + if (iText == null) { + throw new IllegalArgumentException("Filter expression is null"); + } + + context = iContext; + parserText = iText; + parserTextUpperCase = iText.toUpperCase(Locale.ENGLISH); + + try { + final int lastPos = parserGetCurrentPosition(); + final String lastText = parserText; + final String lastTextUpperCase = parserTextUpperCase; + + text(parserText.substring(lastPos)); + + parserText = lastText; + parserTextUpperCase = lastTextUpperCase; + parserMoveCurrentPosition(lastPos); + + } catch (OQueryParsingException e) { + if (e.getText() == null) + // QUERY EXCEPTION BUT WITHOUT TEXT: NEST IT + { + throw OException.wrapException( + new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), e); + } + + throw e; + } catch (Exception e) { + throw OException.wrapException(new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), + e); + } + + this.rootCondition = resetOperatorPrecedence(rootCondition); + } + + private OSQLFilterCondition resetOperatorPrecedence(OSQLFilterCondition iCondition) { + if (iCondition == null) { + return iCondition; + } + if (iCondition.left != null && iCondition.left instanceof OSQLFilterCondition) { + iCondition.left = resetOperatorPrecedence((OSQLFilterCondition) iCondition.left); + } + + if (iCondition.right != null && iCondition.right instanceof OSQLFilterCondition) { + OSQLFilterCondition right = (OSQLFilterCondition) iCondition.right; + iCondition.right = resetOperatorPrecedence(right); + if (iCondition.operator != null) { + if (!right.inBraces && right.operator != null && right.operator.precedence < iCondition.operator.precedence) { + OSQLFilterCondition newLeft = new OSQLFilterCondition(iCondition.left, iCondition.operator, right.left); + right.setLeft(newLeft); + resetOperatorPrecedence(right); + return right; + } + } + } + + return iCondition; + } + + public Object evaluate(final OIdentifiable iRecord, final ODocument iCurrentResult, final OCommandContext iContext) { + if (rootCondition == null) { + return true; + } + + return rootCondition.evaluate(iRecord, iCurrentResult, iContext); + } + + public OSQLFilterCondition getRootCondition() { + return rootCondition; + } + + @Override + public String toString() { + if (rootCondition != null) { + return "Parsed: " + rootCondition.toString(); + } + return "Unparsed: " + parserText; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterCondition.java new file mode 100755 index 00000000000..dcf0a18f30d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterCondition.java @@ -0,0 +1,492 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.config.OStorageConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.query.OQueryRuntimeValueMulti; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.BytesContainer; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorMatches; +import com.orientechnologies.orient.core.sql.query.OSQLQuery; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Pattern; + +/** + * Run-time query condition evaluator. + * + * @author Luca Garulli + */ +public class OSQLFilterCondition { + private static final String NULL_VALUE = "null"; + protected Object left; + protected OQueryOperator operator; + protected Object right; + private OBinaryField rightBinary; + protected boolean inBraces = false; + + public OSQLFilterCondition(final Object iLeft, final OQueryOperator iOperator) { + this.left = iLeft; + this.operator = iOperator; + } + + public OSQLFilterCondition(final Object iLeft, final OQueryOperator iOperator, final Object iRight) { + this.left = iLeft; + this.operator = iOperator; + this.right = iRight; + } + + public Object evaluate(final OIdentifiable iCurrentRecord, final ODocument iCurrentResult, final OCommandContext iContext) { + boolean binaryEvaluation = + operator != null && operator.isSupportingBinaryEvaluate() && iCurrentRecord != null && iCurrentRecord.getIdentity() + .isPersistent(); + + if (left instanceof OSQLQuery) + // EXECUTE SUB QUERIES ONLY ONCE + left = ((OSQLQuery) left).setContext(iContext).execute(); + + Object l = evaluate(iCurrentRecord, iCurrentResult, left, iContext, binaryEvaluation); + + if (operator == null || operator.canShortCircuit(l)) + return l; + + if (right instanceof OSQLQuery) + // EXECUTE SUB QUERIES ONLY ONCE + right = ((OSQLQuery) right).setContext(iContext).execute(); + + Object r = evaluate(iCurrentRecord, iCurrentResult, binaryEvaluation && rightBinary != null ? rightBinary : right, iContext, + binaryEvaluation); + + if (binaryEvaluation && l instanceof OBinaryField) { + if (r != null && !(r instanceof OBinaryField)) { + final OType type = OType.getTypeByValue(r); + + if (ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isBinaryComparable(type)) { + final BytesContainer bytes = new BytesContainer(); + ORecordSerializerBinary.INSTANCE.getCurrentSerializer().serializeValue(bytes, r, type, null); + bytes.offset = 0; + final OCollate collate = r instanceof OSQLFilterItemField ? ((OSQLFilterItemField) r).getCollate(iCurrentRecord) : null; + r = new OBinaryField(null, type, bytes, collate); + if (!(right instanceof OSQLFilterItem || right instanceof OSQLFilterCondition)) { + // FIXED VALUE, REPLACE IT + rightBinary = (OBinaryField) r; + } + } + } else if (r instanceof OBinaryField) + // GET THE COPY OR MT REASONS + r = ((OBinaryField) r).copy(); + } + + if (binaryEvaluation && r instanceof OBinaryField) { + if (l != null && !(l instanceof OBinaryField)) { + final OType type = OType.getTypeByValue(l); + if (ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isBinaryComparable(type)) { + final BytesContainer bytes = new BytesContainer(); + ORecordSerializerBinary.INSTANCE.getCurrentSerializer().serializeValue(bytes, l, type, null); + bytes.offset = 0; + final OCollate collate = l instanceof OSQLFilterItemField ? ((OSQLFilterItemField) l).getCollate(iCurrentRecord) : null; + l = new OBinaryField(null, type, bytes, collate); + if (!(left instanceof OSQLFilterItem || left instanceof OSQLFilterCondition)) + // FIXED VALUE, REPLACE IT + left = l; + } + } else if (l instanceof OBinaryField) + // GET THE COPY OR MT REASONS + l = ((OBinaryField) l).copy(); + } + + if (binaryEvaluation) + binaryEvaluation = l instanceof OBinaryField && r instanceof OBinaryField; + + if (!binaryEvaluation) { + // no collate for regular expressions, otherwise quotes will result in no match + final OCollate collate = operator instanceof OQueryOperatorMatches ? null : getCollate(iCurrentRecord); + final Object[] convertedValues = checkForConversion(iCurrentRecord, l, r, collate); + if (convertedValues != null) { + l = convertedValues[0]; + r = convertedValues[1]; + } + } + + Object result; + try { + result = operator.evaluateRecord(iCurrentRecord, iCurrentResult, this, l, r, iContext); + } catch (OCommandExecutionException e) { + throw e; + } catch (Exception e) { + if (OLogManager.instance().isDebugEnabled()) + OLogManager.instance().debug(this, "Error on evaluating expression (%s)", e, toString()); + result = Boolean.FALSE; + } + + return result; + } + + @Deprecated + public OCollate getCollate() { + if (left instanceof OSQLFilterItemField) { + return ((OSQLFilterItemField) left).getCollate(); + } else if (right instanceof OSQLFilterItemField) { + return ((OSQLFilterItemField) right).getCollate(); + } + return null; + } + + public OCollate getCollate(OIdentifiable doc) { + if (left instanceof OSQLFilterItemField) { + return ((OSQLFilterItemField) left).getCollate(doc); + } else if (right instanceof OSQLFilterItemField) { + return ((OSQLFilterItemField) right).getCollate(doc); + } + return null; + } + + public ORID getBeginRidRange() { + if (operator == null) { + if (left instanceof OSQLFilterCondition) { + return ((OSQLFilterCondition) left).getBeginRidRange(); + } else { + return null; + } + } + + return operator.getBeginRidRange(left, right); + } + + public ORID getEndRidRange() { + if (operator == null) { + if (left instanceof OSQLFilterCondition) { + return ((OSQLFilterCondition) left).getEndRidRange(); + } else { + return null; + } + } + + return operator.getEndRidRange(left, right); + } + + public List getInvolvedFields(final List list) { + extractInvolvedFields(getLeft(), list); + extractInvolvedFields(getRight(), list); + + return list; + } + + private void extractInvolvedFields(Object left, List list) { + if (left != null) { + if (left instanceof OSQLFilterItemField) { + if (((OSQLFilterItemField) left).isFieldChain()) { + list.add(((OSQLFilterItemField) left).getFieldChain() + .getItemName(((OSQLFilterItemField) left).getFieldChain().getItemCount() - 1)); + } + } else if (left instanceof OSQLFilterCondition) { + ((OSQLFilterCondition) left).getInvolvedFields(list); + } + } + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(128); + + buffer.append('('); + buffer.append(left); + if (operator != null) { + buffer.append(' '); + buffer.append(operator); + buffer.append(' '); + if (right instanceof String) { + buffer.append('\''); + } + buffer.append(right); + if (right instanceof String) { + buffer.append('\''); + } + buffer.append(')'); + } + + return buffer.toString(); + } + + public Object getLeft() { + return left; + } + + public void setLeft(final Object iValue) { + left = iValue; + } + + public Object getRight() { + return right; + } + + public void setRight(final Object iValue) { + right = iValue; + } + + public OQueryOperator getOperator() { + return operator; + } + + protected Integer getInteger(Object iValue) { + if (iValue == null) { + return null; + } + + final String stringValue = iValue.toString(); + + if (NULL_VALUE.equals(stringValue)) { + return null; + } + if (OSQLHelper.DEFINED.equals(stringValue)) { + return null; + } + + if (OStringSerializerHelper.contains(stringValue, '.') || OStringSerializerHelper.contains(stringValue, ',')) { + return (int) Float.parseFloat(stringValue); + } else { + return stringValue.length() > 0 ? new Integer(stringValue) : new Integer(0); + } + } + + protected Float getFloat(final Object iValue) { + if (iValue == null) { + return null; + } + + final String stringValue = iValue.toString(); + + if (NULL_VALUE.equals(stringValue)) { + return null; + } + + return stringValue.length() > 0 ? new Float(stringValue) : new Float(0); + } + + protected Date getDate(final Object value) { + if (value == null) { + return null; + } + + final OStorageConfiguration config = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration(); + + if (value instanceof Long) { + Calendar calendar = Calendar.getInstance(config.getTimeZone()); + calendar.setTimeInMillis(((Long) value)); + return calendar.getTime(); + } + + String stringValue = value.toString(); + + if (NULL_VALUE.equals(stringValue)) { + return null; + } + + if (stringValue.length() <= 0) { + return null; + } + + if (Pattern.matches("^\\d+$", stringValue)) { + return new Date(Long.valueOf(stringValue).longValue()); + } + + SimpleDateFormat formatter = config.getDateFormatInstance(); + + if (stringValue.length() > config.dateFormat.length()) + // ASSUMES YOU'RE USING THE DATE-TIME FORMATTE + { + formatter = config.getDateTimeFormatInstance(); + } + + try { + return formatter.parse(stringValue); + } catch (ParseException pe) { + try { + return new Date(new Double(stringValue).longValue()); + } catch (Exception pe2) { + throw OException.wrapException(new OQueryParsingException( + "Error on conversion of date '" + stringValue + "' using the format: " + formatter.toPattern()), pe2); + } + } + } + + protected Object evaluate(OIdentifiable iCurrentRecord, final ODocument iCurrentResult, final Object iValue, + final OCommandContext iContext, final boolean binaryEvaluation) { + if (iValue == null) + return null; + + if (iValue instanceof BytesContainer) + return iValue; + + if (iCurrentRecord != null) { + iCurrentRecord = iCurrentRecord.getRecord(); + if (iCurrentRecord != null && ((ORecord) iCurrentRecord).getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) { + try { + iCurrentRecord = iCurrentRecord.getRecord().load(); + } catch (ORecordNotFoundException e) { + return null; + } + } + } + + if (binaryEvaluation && iValue instanceof OSQLFilterItemField) { + final OBinaryField bField = ((OSQLFilterItemField) iValue).getBinaryField(iCurrentRecord); + if (bField != null) + return bField; + } + + if (iValue instanceof OSQLFilterItem) { + return ((OSQLFilterItem) iValue).getValue(iCurrentRecord, iCurrentResult, iContext); + } + + if (iValue instanceof OSQLFilterCondition) { + // NESTED CONDITION: EVALUATE IT RECURSIVELY + return ((OSQLFilterCondition) iValue).evaluate(iCurrentRecord, iCurrentResult, iContext); + } + + if (iValue instanceof OSQLFunctionRuntime) { + // STATELESS FUNCTION: EXECUTE IT + final OSQLFunctionRuntime f = (OSQLFunctionRuntime) iValue; + return f.execute(iCurrentRecord, iCurrentRecord, iCurrentResult, iContext); + } + + if (OMultiValue.isMultiValue(iValue)) { + final Iterable multiValue = OMultiValue.getMultiValueIterable(iValue, false); + + // MULTI VALUE: RETURN A COPY + final ArrayList result = new ArrayList(OMultiValue.getSize(iValue)); + + for (final Object value : multiValue) { + if (value instanceof OSQLFilterItem) { + result.add(((OSQLFilterItem) value).getValue(iCurrentRecord, iCurrentResult, iContext)); + } else { + result.add(value); + } + } + return result; + } + + // SIMPLE VALUE: JUST RETURN IT + return iValue; + } + + private Object[] checkForConversion(final OIdentifiable o, Object l, Object r, final OCollate collate) { + Object[] result = null; + + final Object oldL = l; + final Object oldR = r; + if (collate != null) { + + l = collate.transform(l); + r = collate.transform(r); + + if (l != oldL || r != oldR) + // CHANGED + { + result = new Object[] { l, r }; + } + } + + try { + // DEFINED OPERATOR + if ((oldR instanceof String && oldR.equals(OSQLHelper.DEFINED)) || (oldL instanceof String && oldL + .equals(OSQLHelper.DEFINED))) { + result = new Object[] { ((OSQLFilterItemAbstract) this.left).getRoot(), r }; + } + + // NOT_NULL OPERATOR + else if ((oldR instanceof String && oldR.equals(OSQLHelper.NOT_NULL)) || (oldL instanceof String && oldL + .equals(OSQLHelper.NOT_NULL))) { + result = null; + } else if (l != null && r != null && !l.getClass().isAssignableFrom(r.getClass()) && !r.getClass() + .isAssignableFrom(l.getClass())) + // INTEGERS + { + if (r instanceof Integer && !(l instanceof Number || l instanceof Collection)) { + if (l instanceof String && ((String) l).indexOf('.') > -1) { + result = new Object[] { new Float((String) l).intValue(), r }; + } else if (l instanceof Date) { + result = new Object[] { ((Date) l).getTime(), r }; + } else if (!(l instanceof OQueryRuntimeValueMulti) && !(l instanceof Collection) && !l.getClass().isArray() + && !(l instanceof Map)) { + result = new Object[] { getInteger(l), r }; + } + } else if (l instanceof Integer && !(r instanceof Number || r instanceof Collection)) { + if (r instanceof String && ((String) r).indexOf('.') > -1) { + result = new Object[] { l, new Float((String) r).intValue() }; + } else if (r instanceof Date) { + result = new Object[] { l, ((Date) r).getTime() }; + } else if (!(r instanceof OQueryRuntimeValueMulti) && !(r instanceof Collection) && !r.getClass().isArray() + && !(r instanceof Map)) { + result = new Object[] { l, getInteger(r) }; + } + } + + // DATES + else if (r instanceof Date && !(l instanceof Collection || l instanceof Date)) { + result = new Object[] { getDate(l), r }; + } else if (l instanceof Date && !(r instanceof Collection || r instanceof Date)) { + result = new Object[] { l, getDate(r) }; + } + + // FLOATS + else if (r instanceof Float && !(l instanceof Float || l instanceof Collection)) { + result = new Object[] { getFloat(l), r }; + } else if (l instanceof Float && !(r instanceof Float || r instanceof Collection)) { + result = new Object[] { l, getFloat(r) }; + } + + // RIDS + else if (r instanceof ORID && l instanceof String && !oldL.equals(OSQLHelper.NOT_NULL)) { + result = new Object[] { new ORecordId((String) l), r }; + } else if (l instanceof ORID && r instanceof String && !oldR.equals(OSQLHelper.NOT_NULL)) { + result = new Object[] { l, new ORecordId((String) r) }; + } + } + } catch (Exception e) { + // JUST IGNORE CONVERSION ERRORS + } + + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItem.java new file mode 100644 index 00000000000..5036991a54b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItem.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Represent a value inside a query condition. + * + * @author Luca Garulli + * + */ +public interface OSQLFilterItem { + + Object getValue(OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContetx); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemAbstract.java new file mode 100755 index 00000000000..cbc1947c7b3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemAbstract.java @@ -0,0 +1,195 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.common.util.OCommonConst; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.coll.OSQLMethodMultiValue; +import com.orientechnologies.orient.core.sql.method.OSQLMethod; +import com.orientechnologies.orient.core.sql.method.misc.OSQLMethodField; +import com.orientechnologies.orient.core.sql.method.misc.OSQLMethodFunctionDelegate; +import com.orientechnologies.orient.core.sql.method.OSQLMethodRuntime; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Represents an object field as value in the query condition. + * + * @author Luca Garulli + * + */ +public abstract class OSQLFilterItemAbstract implements OSQLFilterItem { + + protected List> operationsChain = null; + + protected OSQLFilterItemAbstract() { + } + + public OSQLFilterItemAbstract(final OBaseParser iQueryToParse, final String iText) { + final List parts = OStringSerializerHelper.smartSplit(iText, new char[] { '.', '[', ']' }, new boolean[] { false, + false, true }, new boolean[] { false, true, false }, 0, -1, false, true, false, false, OCommonConst.EMPTY_CHAR_ARRAY); + + setRoot(iQueryToParse, parts.get(0)); + + if (parts.size() > 1) { + operationsChain = new ArrayList>(); + + // GET ALL SPECIAL OPERATIONS + for (int i = 1; i < parts.size(); ++i) { + final String part = parts.get(i); + + final int pindex = part.indexOf('('); + if (part.charAt(0) == '[') + operationsChain.add(new OPair(new OSQLMethodRuntime(OSQLEngine + .getMethod(OSQLMethodMultiValue.NAME)), new Object[] { part })); + else if (pindex > -1) { + final String methodName = part.substring(0, pindex).trim().toLowerCase(Locale.ENGLISH); + + OSQLMethod method = OSQLEngine.getMethod(methodName); + final Object[] arguments; + if (method != null) { + if (method.getMaxParams() == -1 || method.getMaxParams() > 0) { + arguments = OStringSerializerHelper.getParameters(part).toArray(); + if (arguments.length < method.getMinParams() + || (method.getMaxParams() > -1 && arguments.length > method.getMaxParams())) + throw new OQueryParsingException(iQueryToParse.parserText, "Syntax error: field operator '" + + method.getName() + + "' needs " + + (method.getMinParams() == method.getMaxParams() ? method.getMinParams() : method.getMinParams() + "-" + + method.getMaxParams()) + " argument(s) while has been received " + arguments.length, 0); + } else + arguments = null; + + } else { + // LOOK FOR FUNCTION + final OSQLFunction f = OSQLEngine.getInstance().getFunction(methodName); + + if (f == null) + // ERROR: METHOD/FUNCTION NOT FOUND OR MISPELLED + throw new OQueryParsingException(iQueryToParse.parserText, + "Syntax error: function or field operator not recognized between the supported ones: " + + OSQLEngine.getMethodNames(), 0); + + if (f.getMaxParams() == -1 || f.getMaxParams() > 0) { + arguments = OStringSerializerHelper.getParameters(part).toArray(); + if (arguments.length + 1 < f.getMinParams() || (f.getMaxParams() > -1 && arguments.length + 1 > f.getMaxParams())) + throw new OQueryParsingException(iQueryToParse.parserText, "Syntax error: function '" + f.getName() + "' needs " + + (f.getMinParams() == f.getMaxParams() ? f.getMinParams() : f.getMinParams() + "-" + f.getMaxParams()) + + " argument(s) while has been received " + arguments.length, 0); + } else + arguments = null; + + method = new OSQLMethodFunctionDelegate(f); + } + + final OSQLMethodRuntime runtimeMethod = new OSQLMethodRuntime(method); + + // SPECIAL OPERATION FOUND: ADD IT IN TO THE CHAIN + operationsChain.add(new OPair(runtimeMethod, arguments)); + + } else { + operationsChain.add(new OPair(new OSQLMethodRuntime(OSQLEngine + .getMethod(OSQLMethodField.NAME)), new Object[] { part })); + } + } + } + } + + public abstract String getRoot(); + + public Object transformValue(final OIdentifiable iRecord, final OCommandContext iContext, Object ioResult) { + if (ioResult != null && operationsChain != null) { + // APPLY OPERATIONS FOLLOWING THE STACK ORDER + OSQLMethodRuntime method = null; + + for (OPair op : operationsChain) { + method = op.getKey(); + + // DON'T PASS THE CURRENT RECORD TO FORCE EVALUATING TEMPORARY RESULT + method.setParameters(op.getValue(), true); + + ioResult = method.execute(ioResult, iRecord, ioResult, iContext); + } + } + + return ioResult; + } + + public boolean hasChainOperators() { + return operationsChain != null; + } + + public OPair getLastChainOperator() { + if (operationsChain != null) + return operationsChain.get(operationsChain.size() - 1); + + return null; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(128); + final String root = getRoot(); + if (root != null) + buffer.append(root); + if (operationsChain != null) { + for (OPair op : operationsChain) { + buffer.append('.'); + buffer.append(op.getKey()); + if (op.getValue() != null) { + final Object[] values = op.getValue(); + buffer.append('('); + int i = 0; + for (Object v : values) { + if (i++ > 0) + buffer.append(','); + buffer.append(v); + } + buffer.append(')'); + } + } + } + return buffer.toString(); + } + + protected abstract void setRoot(OBaseParser iQueryToParse, final String iRoot); + + protected OCollate getCollateForField(final OClass iClass, final String iFieldName) { + if (iClass != null) { + final OProperty p = iClass.getProperty(iFieldName); + if (p != null) + return p.getCollate(); + } + return null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemField.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemField.java new file mode 100755 index 00000000000..164beea1793 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemField.java @@ -0,0 +1,258 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.BytesContainer; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.method.OSQLMethodRuntime; +import com.orientechnologies.orient.core.sql.method.misc.OSQLMethodField; + +import java.util.Set; + +/** + * Represent an object field as value in the query condition. + * + * @author Luca Garulli + */ +public class OSQLFilterItemField extends OSQLFilterItemAbstract { + + protected Set preLoadedFields; + protected String[] preLoadedFieldsArray; + protected String name; + protected OCollate collate; + private boolean collatePreset = false; + private String stringValue; + + /** + * Represents filter item as chain of fields. Provide interface to work with this chain like with sequence of field names. + */ + public class FieldChain { + private FieldChain() { + } + + public String getItemName(int fieldIndex) { + if (fieldIndex == 0) { + return name; + } else { + return operationsChain.get(fieldIndex - 1).getValue()[0].toString(); + } + } + + public int getItemCount() { + if (operationsChain == null) { + return 1; + } else { + return operationsChain.size() + 1; + } + } + + /** + * Field chain is considered as long chain if it contains more than one item. + * + * @return true if this chain is long and false in another case. + */ + public boolean isLong() { + return operationsChain != null && operationsChain.size() > 0; + } + + public boolean belongsTo(OSQLFilterItemField filterItemField) { + return OSQLFilterItemField.this == filterItemField; + } + } + + public OSQLFilterItemField(final String iName, final OClass iClass) { + this.name = OIOUtils.getStringContent(iName); + collate = getCollateForField(iClass, name); + if (iClass != null) { + collatePreset = true; + } + } + + public OSQLFilterItemField(final OBaseParser iQueryToParse, final String iName, final OClass iClass) { + super(iQueryToParse, iName); + collate = getCollateForField(iClass, iName); + if (iClass != null) { + collatePreset = true; + } + } + + public Object getValue(final OIdentifiable iRecord, final Object iCurrentResult, final OCommandContext iContext) { + if (iRecord == null) + throw new OCommandExecutionException("expression item '" + name + "' cannot be resolved because current record is NULL"); + + if (preLoadedFields != null && preLoadedFields.size() == 1) { + if ("@rid".equalsIgnoreCase(preLoadedFields.iterator().next())) + return transformValue(iRecord, iContext, iRecord.getIdentity()); + } + + final ODocument doc = (ODocument) iRecord.getRecord(); + + if (preLoadedFieldsArray == null && preLoadedFields != null && preLoadedFields.size() > 0 && preLoadedFields.size() < 5) { + // TRANSFORM THE SET IN ARRAY ONLY THE FIRST TIME AND IF FIELDS ARE MORE THAN ONE, OTHERWISE GO WITH THE DEFAULT BEHAVIOR + preLoadedFieldsArray = new String[preLoadedFields.size()]; + preLoadedFields.toArray(preLoadedFieldsArray); + } + + // UNMARSHALL THE SINGLE FIELD + if (preLoadedFieldsArray != null && !doc.deserializeFields(preLoadedFieldsArray)) + return null; + + final Object v = stringValue == null ? doc.rawField(name) : stringValue; + + if (!collatePreset && doc != null) { + OClass schemaClass = doc.getSchemaClass(); + if (schemaClass != null) { + collate = getCollateForField(schemaClass, name); + } + } + + return transformValue(iRecord, iContext, v); + } + + public OBinaryField getBinaryField(final OIdentifiable iRecord) { + if (iRecord == null) + throw new OCommandExecutionException("expression item '" + name + "' cannot be resolved because current record is NULL"); + + if (operationsChain != null && operationsChain.size() > 0) + // CANNOT USE BINARY FIELDS + return null; + + final ORecord rec = iRecord.getRecord(); + + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().deserializeField(new BytesContainer(rec.toStream()).skip(1), + rec instanceof ODocument ? ((ODocument) rec).getSchemaClass() : null, name); + } + + public String getRoot() { + return name; + } + + public void setRoot(final OBaseParser iQueryToParse, final String iRoot) { + if (isStringLiteral(iRoot)) { + this.stringValue = OIOUtils.getStringContent(iRoot); + } + //TODO support all the basic types + this.name = OIOUtils.getStringContent(iRoot); + } + + private boolean isStringLiteral(String iRoot) { + if (iRoot.startsWith("'") && iRoot.endsWith("'")) { + return true; + } + if (iRoot.startsWith("\"") && iRoot.endsWith("\"")) { + return true; + } + return false; + } + + /** + * Check whether or not this filter item is chain of fields (e.g. "field1.field2.field3"). Return true if filter item contains + * only field projections operators, if field item contains any other projection operator the method returns false. When filter + * item does not contains any chain operator, it is also field chain consist of one field. + * + * @return whether or not this filter item can be represented as chain of fields. + */ + public boolean isFieldChain() { + if (operationsChain == null) { + return true; + } + + for (OPair pair : operationsChain) { + if (!pair.getKey().getMethod().getName().equals(OSQLMethodField.NAME)) { + return false; + } + } + + return true; + } + + /** + * Creates {@code FieldChain} in case when filter item can have such representation. + * + * @return {@code FieldChain} representation of this filter item. + * @throws IllegalStateException if this filter item cannot be represented as {@code FieldChain}. + */ + public FieldChain getFieldChain() { + if (!isFieldChain()) { + throw new IllegalStateException("Filter item field contains not only field operators"); + } + + return new FieldChain(); + } + + public void setPreLoadedFields(final Set iPrefetchedFieldList) { + this.preLoadedFields = iPrefetchedFieldList; + } + + public OCollate getCollate() { + return collate; + } + + /** + * get the collate of this expression, based on the fully evaluated field chain starting from the passed object. + * + * @param doc the root element (document?) of this field chain + * @return the collate, null if no collate is defined + */ + public OCollate getCollate(Object doc) { + if (collate != null || operationsChain == null || !isFieldChain()) { + return collate; + } + if (!(doc instanceof OIdentifiable)) { + return null; + } + FieldChain chain = getFieldChain(); + ODocument lastDoc = ((OIdentifiable) doc).getRecord(); + for (int i = 0; i < chain.getItemCount() - 1; i++) { + if (lastDoc == null) { + return null; + } + Object nextDoc = lastDoc.field(chain.getItemName(i)); + if (nextDoc == null || !(nextDoc instanceof OIdentifiable)) { + return null; + } + lastDoc = ((OIdentifiable) nextDoc).getRecord(); + } + if (lastDoc == null) { + return null; + } + OClass schemaClass = lastDoc.getSchemaClass(); + if (schemaClass == null) { + return null; + } + OProperty property = schemaClass.getProperty(chain.getItemName(chain.getItemCount() - 1)); + if (property == null) { + return null; + } + return property.getCollate(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAll.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAll.java new file mode 100644 index 00000000000..0b357f0c972 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAll.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +/** + * Represent one or more object fields as value in the query condition. + * + * @author Luca Garulli + * + */ +public class OSQLFilterItemFieldAll extends OSQLFilterItemFieldMultiAbstract { + public static final String NAME = "ALL"; + public static final String FULL_NAME = "ALL()"; + + public OSQLFilterItemFieldAll(final OSQLPredicate iQueryCompiled, final String iName, final OClass iClass) { + super(iQueryCompiled, iName, iClass, OStringSerializerHelper.getParameters(iName)); + } + + @Override + public String getRoot() { + return FULL_NAME; + } + + @Override + protected void setRoot(final OBaseParser iQueryToParse, final String iRoot) { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAny.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAny.java new file mode 100644 index 00000000000..90e17528b1c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldAny.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +/** + * Represent one or more object fields as value in the query condition. + * + * @author Luca Garulli + * + */ +public class OSQLFilterItemFieldAny extends OSQLFilterItemFieldMultiAbstract { + public static final String NAME = "ANY"; + public static final String FULL_NAME = "ANY()"; + + public OSQLFilterItemFieldAny(final OSQLPredicate iQueryCompiled, final String iName, final OClass iClass) { + super(iQueryCompiled, iName, iClass, OStringSerializerHelper.getParameters(iName)); + } + + @Override + public String getRoot() { + return FULL_NAME; + } + + @Override + protected void setRoot(final OBaseParser iQueryToParse, final String iRoot) { + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldMultiAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldMultiAbstract.java new file mode 100755 index 00000000000..0ba5400ebab --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemFieldMultiAbstract.java @@ -0,0 +1,78 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.query.OQueryRuntimeValueMulti; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents one or more object fields as value in the query condition. + * + * @author Luca Garulli + * + */ +public abstract class OSQLFilterItemFieldMultiAbstract extends OSQLFilterItemAbstract { + private List names; + private final OClass clazz; + private final List collates = new ArrayList(); + + public OSQLFilterItemFieldMultiAbstract(final OSQLPredicate iQueryCompiled, final String iName, final OClass iClass, + final List iNames) { + super(iQueryCompiled, iName); + names = iNames; + clazz = iClass; + + for (String n : iNames) { + collates.add(getCollateForField(iClass, n)); + } + } + + public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContext) { + final ODocument doc = ((ODocument) iRecord); + + if (names.size() == 1) + return transformValue(iRecord, iContext, ODocumentHelper.getIdentifiableValue(iRecord, names.get(0))); + + final String[] fieldNames = doc.fieldNames(); + final Object[] values = new Object[fieldNames.length]; + + collates.clear(); + for (int i = 0; i < values.length; ++i) { + values[i] = doc.field(fieldNames[i]); + collates.add(getCollateForField(clazz, fieldNames[i])); + } + + if (hasChainOperators()) { + // TRANSFORM ALL THE VALUES + for (int i = 0; i < values.length; ++i) + values[i] = transformValue(iRecord, iContext, values[i]); + } + + return new OQueryRuntimeValueMulti(this, values, collates); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemParameter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemParameter.java new file mode 100644 index 00000000000..38d88db02e5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemParameter.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Represents a constant value, used to bind parameters. + * + * @author Luca Garulli + * + */ +public class OSQLFilterItemParameter implements OSQLFilterItem { + private final String name; + private Object value = NOT_SETTED; + + private static final String NOT_SETTED = "?"; + + public OSQLFilterItemParameter(final String iName) { + this.name = iName; + } + + public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContetx) { + return value; + } + + @Override + public String toString() { + if (value == NOT_SETTED) + return name.equals("?") ? "?" : ":" + name; + else + return value == null ? "null" : value.toString(); + } + + public String getName() { + return name; + } + + public void setValue(Object value) { + this.value = value; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemVariable.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemVariable.java new file mode 100644 index 00000000000..a07238dbe0b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLFilterItemVariable.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Represents a context variable as value in the query condition. + * + * @author Luca Garulli + * + */ +public class OSQLFilterItemVariable extends OSQLFilterItemAbstract { + protected String name; + + public OSQLFilterItemVariable(final OBaseParser iQueryToParse, final String iName) { + super(iQueryToParse, iName.substring(1)); + } + + public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, final OCommandContext iContext) { + if (iContext == null) + return null; + + return transformValue(iRecord, iContext, iContext.getVariable(name)); + } + + public String getRoot() { + return name; + } + + public void setRoot(final OBaseParser iQueryToParse, final String iRoot) { + this.name = iRoot; + } + + @Override + public String toString() { + return "$" + super.toString(); + } + + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLPredicate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLPredicate.java new file mode 100755 index 00000000000..68e1d0b1922 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLPredicate.java @@ -0,0 +1,437 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandPredicate; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorAnd; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorNot; +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorOr; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.util.*; + +/** + * Parses text in SQL format and build a tree of conditions. + * + * @author Luca Garulli + */ +public class OSQLPredicate extends OBaseParser implements OCommandPredicate { + protected Set properties = new HashSet(); + protected OSQLFilterCondition rootCondition; + protected List recordTransformed; + protected List parameterItems; + protected int braces; + protected OCommandContext context; + + public OSQLPredicate() { + } + + public OSQLPredicate(final String iText) { + text(iText); + } + + protected void throwSyntaxErrorException(final String iText) { + final String syntax = getSyntax(); + if (syntax.equals("?")) + throw new OCommandSQLParsingException(iText, parserText, parserGetPreviousPosition()); + + throw new OCommandSQLParsingException(iText + ". Use " + syntax, parserText, parserGetPreviousPosition()); + } + + protected String upperCase(String text) { + // TODO remove and refactor (see same method in OCommandExecutorAbstract) + StringBuilder result = new StringBuilder(text.length()); + for (char c : text.toCharArray()) { + String upper = ("" + c).toUpperCase(Locale.ENGLISH); + if (upper.length() > 1) { + result.append(c); + } else { + result.append(upper); + } + } + return result.toString(); + } + + public OSQLPredicate text(final String iText) { + if (iText == null) + throw new OCommandSQLParsingException("Query text is null"); + + try { + parserText = iText; + parserTextUpperCase = upperCase(parserText); + parserSetCurrentPosition(0); + parserSkipWhiteSpaces(); + + rootCondition = (OSQLFilterCondition) extractConditions(null); + + optimize(); + } catch (OQueryParsingException e) { + if (e.getText() == null) + // QUERY EXCEPTION BUT WITHOUT TEXT: NEST IT + throw OException + .wrapException(new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), e); + + throw e; + } catch (Exception t) { + throw OException + .wrapException(new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), t); + } + return this; + } + + public Object evaluate() { + return evaluate(null, null, null); + } + + public Object evaluate(final OCommandContext iContext) { + return evaluate(null, null, iContext); + } + + public Object evaluate(final OIdentifiable iRecord, ODocument iCurrentResult, final OCommandContext iContext) { + if (rootCondition == null) + return true; + + return rootCondition.evaluate(iRecord, iCurrentResult, iContext); + } + + protected Object extractConditions(final OSQLFilterCondition iParentCondition) { + final int oldPosition = parserGetCurrentPosition(); + parserNextWord(true, " )=><,\r\n", true); + final String word = parserGetLastWord(); + + boolean inBraces = word.length() > 0 && word.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN; + + if (word.length() > 0 && (word.equalsIgnoreCase("SELECT") || word.equalsIgnoreCase("TRAVERSE"))) { + // SUB QUERY + final StringBuilder embedded = new StringBuilder(256); + OStringSerializerHelper.getEmbedded(parserText, oldPosition - 1, -1, embedded); + parserSetCurrentPosition(oldPosition + embedded.length() + 1); + return new OSQLSynchQuery(embedded.toString()); + } + + parserSetCurrentPosition(oldPosition); + OSQLFilterCondition currentCondition = extractCondition(); + + // CHECK IF THERE IS ANOTHER CONDITION ON RIGHT + while (parserSkipWhiteSpaces()) { + + if (!parserIsEnded() && parserGetCurrentChar() == ')') + return currentCondition; + + final OQueryOperator nextOperator = extractConditionOperator(); + if (nextOperator == null) + return currentCondition; + + if (nextOperator.precedence > currentCondition.getOperator().precedence) { + // SWAP ITEMS + final OSQLFilterCondition subCondition = new OSQLFilterCondition(currentCondition.right, nextOperator); + currentCondition.right = subCondition; + subCondition.right = extractConditionItem(false, 1); + } else { + final OSQLFilterCondition parentCondition = new OSQLFilterCondition(currentCondition, nextOperator); + parentCondition.right = extractConditions(parentCondition); + currentCondition = parentCondition; + } + } + + currentCondition.inBraces = inBraces; + + // END OF TEXT + return currentCondition; + } + + protected OSQLFilterCondition extractCondition() { + + if (!parserSkipWhiteSpaces()) + // END OF TEXT + return null; + + // EXTRACT ITEMS + Object left = extractConditionItem(true, 1); + + if (left != null && checkForEnd(left.toString())) + return null; + + OQueryOperator oper; + final Object right; + + if (left instanceof OQueryOperator && ((OQueryOperator) left).isUnary()) { + oper = (OQueryOperator) left; + left = extractConditionItem(false, 1); + right = null; + } else { + oper = extractConditionOperator(); + + if (oper instanceof OQueryOperatorNot) + // SPECIAL CASE: READ NEXT OPERATOR + oper = new OQueryOperatorNot(extractConditionOperator()); + if (oper instanceof OQueryOperatorAnd || oper instanceof OQueryOperatorOr) { + right = extractCondition(); + } else { + right = oper != null ? extractConditionItem(false, oper.expectedRightWords) : null; + } + } + + // CREATE THE CONDITION OBJECT + return new OSQLFilterCondition(left, oper, right); + } + + protected boolean checkForEnd(final String iWord) { + if (iWord != null && (iWord.equals(OCommandExecutorSQLSelect.KEYWORD_ORDER) || iWord + .equals(OCommandExecutorSQLSelect.KEYWORD_LIMIT) || iWord.equals(OCommandExecutorSQLSelect.KEYWORD_SKIP) || iWord + .equals(OCommandExecutorSQLSelect.KEYWORD_OFFSET))) { + parserMoveCurrentPosition(iWord.length() * -1); + return true; + } + return false; + } + + private OQueryOperator extractConditionOperator() { + if (!parserSkipWhiteSpaces()) + // END OF PARSING: JUST RETURN + return null; + + if (parserGetCurrentChar() == ')') + // FOUND ')': JUST RETURN + return null; + + final OQueryOperator[] operators = OSQLEngine.getInstance().getRecordOperators(); + final String[] candidateOperators = new String[operators.length]; + for (int i = 0; i < candidateOperators.length; ++i) + candidateOperators[i] = operators[i].keyword; + + final int operatorPos = parserNextChars(true, false, candidateOperators); + + if (operatorPos == -1) { + parserGoBack(); + return null; + } + + final OQueryOperator op = operators[operatorPos]; + if (op.expectsParameters) { + // PARSE PARAMETERS IF ANY + parserGoBack(); + + parserNextWord(true, " 0123456789'\""); + final String word = parserGetLastWord(); + + final List params = new ArrayList(); + // CHECK FOR PARAMETERS + if (word.length() > op.keyword.length() && word.charAt(op.keyword.length()) == OStringSerializerHelper.EMBEDDED_BEGIN) { + int paramBeginPos = parserGetCurrentPosition() - (word.length() - op.keyword.length()); + parserSetCurrentPosition(OStringSerializerHelper.getParameters(parserText, paramBeginPos, -1, params)); + } else if (!word.equals(op.keyword)) + throw new OQueryParsingException("Malformed usage of operator '" + op.toString() + "'. Parsed operator is: " + word); + + try { + // CONFIGURE COULD INSTANTIATE A NEW OBJECT: ACT AS A FACTORY + return op.configure(params); + } catch (Exception e) { + throw OException.wrapException( + new OQueryParsingException("Syntax error using the operator '" + op.toString() + "'. Syntax is: " + op.getSyntax()), e); + } + } else + parserMoveCurrentPosition(+1); + return op; + } + + private Object extractConditionItem(final boolean iAllowOperator, final int iExpectedWords) { + final Object[] result = new Object[iExpectedWords]; + + for (int i = 0; i < iExpectedWords; ++i) { + parserNextWord(false, " =><,\r\n", true); + String word = parserGetLastWord(); + + if (word.length() == 0) + break; + + final String uWord = word.toUpperCase(Locale.ENGLISH); + + final int lastPosition = parserIsEnded() ? parserText.length() : parserGetCurrentPosition(); + + if (word.length() > 0 && word.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN) { + braces++; + + // SUB-CONDITION + parserSetCurrentPosition(lastPosition - word.length() + 1); + + final Object subCondition = extractConditions(null); + + if (!parserSkipWhiteSpaces() || parserGetCurrentChar() == ')') { + braces--; + parserMoveCurrentPosition(+1); + } + if (subCondition instanceof OSQLFilterCondition) { + ((OSQLFilterCondition) subCondition).inBraces = true; + } + result[i] = subCondition; + } else if (word.charAt(0) == OStringSerializerHelper.LIST_BEGIN) { + // COLLECTION OF ELEMENTS + parserSetCurrentPosition(lastPosition - getLastWordLength()); + + final List stringItems = new ArrayList(); + parserSetCurrentPosition(OStringSerializerHelper.getCollection(parserText, parserGetCurrentPosition(), stringItems)); + result[i] = convertCollectionItems(stringItems); + + parserMoveCurrentPosition(+1); + + } else if (uWord.startsWith(OSQLFilterItemFieldAll.NAME + OStringSerializerHelper.EMBEDDED_BEGIN)) { + + result[i] = new OSQLFilterItemFieldAll(this, word, null); + + } else if (uWord.startsWith(OSQLFilterItemFieldAny.NAME + OStringSerializerHelper.EMBEDDED_BEGIN)) { + + result[i] = new OSQLFilterItemFieldAny(this, word, null); + + } else { + + if (uWord.equals("NOT")) { + if (iAllowOperator) + return new OQueryOperatorNot(); + else { + // GET THE NEXT VALUE + parserNextWord(false, " )=><,\r\n"); + final String nextWord = parserGetLastWord(); + + if (nextWord.length() > 0) { + word += " " + nextWord; + + if (word.endsWith(")")) + word = word.substring(0, word.length() - 1); + } + } + } else if (uWord.equals("AND")) + // SPECIAL CASE IN "BETWEEN X AND Y" + result[i] = word; + + while (word.endsWith(")")) { + final int openParenthesis = word.indexOf('('); + if (openParenthesis == -1) { + // DISCARD END PARENTHESIS + word = word.substring(0, word.length() - 1); + parserMoveCurrentPosition(-1); + } else + break; + } + + result[i] = OSQLHelper.parseValue(this, this, word, context); + } + } + + return iExpectedWords == 1 ? result[0] : result; + } + + private List convertCollectionItems(List stringItems) { + List coll = new ArrayList(); + for (String s : stringItems) { + coll.add(OSQLHelper.parseValue(this, this, s, context)); + } + return coll; + } + + public OSQLFilterCondition getRootCondition() { + return rootCondition; + } + + @Override + public String toString() { + if (rootCondition != null) + return "Parsed: " + rootCondition.toString(); + return "Unparsed: " + parserText; + } + + /** + * Binds parameters. + */ + public void bindParameters(final Map iArgs) { + if (parameterItems == null || iArgs == null || iArgs.size() == 0) + return; + + for (int i = 0; i < parameterItems.size(); i++) { + OSQLFilterItemParameter value = parameterItems.get(i); + if ("?".equals(value.getName())) { + value.setValue(iArgs.get(i)); + } else { + value.setValue(iArgs.get(value.getName())); + } + } + } + + public OSQLFilterItemParameter addParameter(final String iName) { + final String name; + if (iName.charAt(0) == OStringSerializerHelper.PARAMETER_NAMED) { + name = iName.substring(1); + + // CHECK THE PARAMETER NAME IS CORRECT + if (!OStringSerializerHelper.isAlphanumeric(name)) { + throw new OQueryParsingException("Parameter name '" + name + "' is invalid, only alphanumeric characters are allowed"); + } + } else + name = iName; + + final OSQLFilterItemParameter param = new OSQLFilterItemParameter(name); + + if (parameterItems == null) + parameterItems = new ArrayList(); + + parameterItems.add(param); + return param; + } + + public void setRootCondition(final OSQLFilterCondition iCondition) { + rootCondition = iCondition; + } + + protected void optimize() { + if (rootCondition != null) + computePrefetchFieldList(rootCondition, new HashSet()); + } + + protected Set computePrefetchFieldList(final OSQLFilterCondition iCondition, final Set iFields) { + Object left = iCondition.getLeft(); + Object right = iCondition.getRight(); + if (left instanceof OSQLFilterItemField) { + ((OSQLFilterItemField) left).setPreLoadedFields(iFields); + iFields.add(((OSQLFilterItemField) left).getRoot()); + } else if (left instanceof OSQLFilterCondition) + computePrefetchFieldList((OSQLFilterCondition) left, iFields); + + if (right instanceof OSQLFilterItemField) { + ((OSQLFilterItemField) right).setPreLoadedFields(iFields); + iFields.add(((OSQLFilterItemField) right).getRoot()); + } else if (right instanceof OSQLFilterCondition) + computePrefetchFieldList((OSQLFilterCondition) right, iFields); + + return iFields; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLTarget.java b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLTarget.java new file mode 100755 index 00000000000..91c72d19d9b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/filter/OSQLTarget.java @@ -0,0 +1,282 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.filter; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandManager; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.*; + +import java.util.*; + +/** + * Target parser. + * + * @author Luca Garulli + * + */ +public class OSQLTarget extends OBaseParser { + protected final boolean empty; + protected final OCommandContext context; + protected String targetVariable; + protected String targetQuery; + protected Iterable targetRecords; + protected Map targetClusters; + protected Map targetClasses; + + protected String targetIndex; + + protected String targetIndexValues; + protected boolean targetIndexValuesAsc; + + public OSQLTarget(final String iText, final OCommandContext iContext) { + super(); + context = iContext; + parserText = iText; + parserTextUpperCase = upperCase(iText); + + try { + empty = !extractTargets(); + + } catch (OQueryParsingException e) { + if (e.getText() == null) + // QUERY EXCEPTION BUT WITHOUT TEXT: NEST IT + throw OException.wrapException( + new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), e); + + throw e; + } catch (Exception e) { + throw OException.wrapException(new OQueryParsingException("Error on parsing query", parserText, parserGetCurrentPosition()), + e); + } + } + + protected String upperCase(String text) { + // TODO remove and refactor (see same method in OCommandExecutorAbstract) + StringBuilder result = new StringBuilder(text.length()); + for (char c : text.toCharArray()) { + String upper = ("" + c).toUpperCase(Locale.ENGLISH); + if (upper.length() > 1) { + result.append(c); + } else { + result.append(upper); + } + } + return result.toString(); + } + + public Map getTargetClusters() { + return targetClusters; + } + + public Map getTargetClasses() { + return targetClasses; + } + + public Iterable getTargetRecords() { + return targetRecords; + } + + public String getTargetQuery() { + return targetQuery; + } + + public String getTargetIndex() { + return targetIndex; + } + + public String getTargetIndexValues() { + return targetIndexValues; + } + + public boolean isTargetIndexValuesAsc() { + return targetIndexValuesAsc; + } + + @Override + public String toString() { + if (targetClasses != null) + return "class " + targetClasses.keySet(); + else if (targetClusters != null) + return "cluster " + targetClusters.keySet(); + if (targetIndex != null) + return "index " + targetIndex; + if (targetRecords != null) + return "records from " + targetRecords.getClass().getSimpleName(); + if (targetVariable != null) + return "variable " + targetVariable; + return "?"; + } + + public String getTargetVariable() { + return targetVariable; + } + + public boolean isEmpty() { + return empty; + } + + @Override + protected void throwSyntaxErrorException(String iText) { + throw new OCommandSQLParsingException(iText + ". Use " + getSyntax(), parserText, parserGetPreviousPosition()); + } + + @SuppressWarnings("unchecked") + private boolean extractTargets() { + parserSkipWhiteSpaces(); + + if (parserIsEnded()) + throw new OQueryParsingException("No query target found", parserText, 0); + + final char c = parserGetCurrentChar(); + + if (c == '$') { + targetVariable = parserRequiredWord(false, "No valid target"); + targetVariable = targetVariable.substring(1); + } else if (c == OStringSerializerHelper.LINK || Character.isDigit(c)) { + // UNIQUE RID + targetRecords = new ArrayList(); + ((List) targetRecords).add(new ORecordId(parserRequiredWord(true, "No valid RID"))); + + } else if (c == OStringSerializerHelper.EMBEDDED_BEGIN) { + // SUB QUERY + final StringBuilder subText = new StringBuilder(256); + parserSetCurrentPosition(OStringSerializerHelper.getEmbedded(parserText, parserGetCurrentPosition(), -1, subText) + 1); + final OCommandSQL subCommand = new OCommandSQLResultset(subText.toString()); + + final OCommandExecutorSQLResultsetDelegate executor = (OCommandExecutorSQLResultsetDelegate) OCommandManager.instance() + .getExecutor(subCommand); + executor.setProgressListener(subCommand.getProgressListener()); + executor.parse(subCommand); + OCommandContext childContext = executor.getContext(); + if(childContext!=null) { + childContext.setParent(context); + } + + if (!(executor instanceof Iterable)) + throw new OCommandSQLParsingException("Sub-query cannot be iterated because doesn't implement the Iterable interface: " + + subCommand); + + targetQuery = subText.toString(); + targetRecords = executor; + + } else if (c == OStringSerializerHelper.LIST_BEGIN) { + // COLLECTION OF RIDS + final List rids = new ArrayList(); + parserSetCurrentPosition(OStringSerializerHelper.getCollection(parserText, parserGetCurrentPosition(), rids)); + + targetRecords = new ArrayList(); + for (String rid : rids) + ((List) targetRecords).add(new ORecordId(rid)); + + parserMoveCurrentPosition(1); + } else { + + while (!parserIsEnded() + && (targetClasses == null && targetClusters == null && targetIndex == null && targetIndexValues == null && targetRecords == null)) { + String originalSubjectName = parserRequiredWord(false, "Target not found"); + String subjectName = originalSubjectName.toUpperCase(Locale.ENGLISH); + + final String alias; + if (subjectName.equals("AS")) + alias = parserRequiredWord(true, "Alias not found"); + else + alias = subjectName; + + final String subjectToMatch = subjectName; + if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.CLUSTER_PREFIX)) { + // REGISTER AS CLUSTER + if (targetClusters == null) + targetClusters = new HashMap(); + final String clusterNames = subjectName.substring(OCommandExecutorSQLAbstract.CLUSTER_PREFIX.length()); + if (clusterNames.startsWith("[") && clusterNames.endsWith("]")) { + final Collection clusters = new HashSet(3); + OStringSerializerHelper.getCollection(clusterNames, 0, clusters); + for (String cl : clusters) { + targetClusters.put(cl, cl); + } + } else + targetClusters.put(clusterNames, alias); + + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.INDEX_PREFIX)) { + // REGISTER AS INDEX + targetIndex = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_PREFIX.length()); + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.METADATA_PREFIX)) { + // METADATA + final String metadataTarget = subjectName.substring(OCommandExecutorSQLAbstract.METADATA_PREFIX.length()); + targetRecords = new ArrayList(); + + if (metadataTarget.equals(OCommandExecutorSQLAbstract.METADATA_SCHEMA)) { + ((ArrayList) targetRecords).add(new ORecordId(ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() + .getConfiguration().schemaRecordId)); + } else if (metadataTarget.equals(OCommandExecutorSQLAbstract.METADATA_INDEXMGR)) { + ((ArrayList) targetRecords).add(new ORecordId(ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() + .getConfiguration().indexMgrRecordId)); + } else + throw new OQueryParsingException("Metadata element not supported: " + metadataTarget); + + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.DICTIONARY_PREFIX)) { + // DICTIONARY + final String key = originalSubjectName.substring(OCommandExecutorSQLAbstract.DICTIONARY_PREFIX.length()); + targetRecords = new ArrayList(); + + final OIdentifiable value = ODatabaseRecordThreadLocal.INSTANCE.get().getDictionary().get(key); + if (value != null) + ((List) targetRecords).add(value); + + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.INDEX_VALUES_PREFIX)) { + targetIndexValues = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_VALUES_PREFIX.length()); + targetIndexValuesAsc = true; + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.INDEX_VALUES_ASC_PREFIX)) { + targetIndexValues = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_VALUES_ASC_PREFIX.length()); + targetIndexValuesAsc = true; + } else if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.INDEX_VALUES_DESC_PREFIX)) { + targetIndexValues = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_VALUES_DESC_PREFIX.length()); + targetIndexValuesAsc = false; + } else { + if (subjectToMatch.startsWith(OCommandExecutorSQLAbstract.CLASS_PREFIX)) + // REGISTER AS CLASS + subjectName = subjectName.substring(OCommandExecutorSQLAbstract.CLASS_PREFIX.length()); + + // REGISTER AS CLASS + if (targetClasses == null) + targetClasses = new HashMap(); + + final OClass cls = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().getClass(subjectName); + if (cls == null) + throw new OCommandExecutionException("Class '" + subjectName + "' was not found in database '" + + ODatabaseRecordThreadLocal.INSTANCE.get().getName() + "'"); + + targetClasses.put(cls.getName(), alias); + } + } + } + + return !parserIsEnded(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/ODefaultSQLFunctionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/ODefaultSQLFunctionFactory.java new file mode 100755 index 00000000000..19a40ef72e2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/ODefaultSQLFunctionFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.functions.coll.*; +import com.orientechnologies.orient.core.sql.functions.geo.OSQLFunctionDistance; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionAbsoluteValue; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionAverage; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionDecimal; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionEval; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionMax; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionMin; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionSum; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionCoalesce; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionCount; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionDate; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionDecode; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionEncode; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionIf; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionIfNull; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionSysdate; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLFunctionUUID; +import com.orientechnologies.orient.core.sql.functions.sequence.OSQLFunctionSequence; +import com.orientechnologies.orient.core.sql.functions.stat.OSQLFunctionMedian; +import com.orientechnologies.orient.core.sql.functions.stat.OSQLFunctionMode; +import com.orientechnologies.orient.core.sql.functions.stat.OSQLFunctionPercentile; +import com.orientechnologies.orient.core.sql.functions.stat.OSQLFunctionStandardDeviation; +import com.orientechnologies.orient.core.sql.functions.stat.OSQLFunctionVariance; +import com.orientechnologies.orient.core.sql.functions.text.OSQLFunctionConcat; +import com.orientechnologies.orient.core.sql.functions.text.OSQLFunctionFormat; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Default set of SQL function. + * + * @author Johann Sorel (Geomatys) + */ +public final class ODefaultSQLFunctionFactory implements OSQLFunctionFactory { + + private static final Map FUNCTIONS = new HashMap(); + static { + // MISC FUNCTIONS + register(OSQLFunctionAverage.NAME, OSQLFunctionAverage.class); + register(OSQLFunctionCoalesce.NAME, new OSQLFunctionCoalesce()); + register(OSQLFunctionCount.NAME, OSQLFunctionCount.class); + register(OSQLFunctionDate.NAME, OSQLFunctionDate.class); + register(OSQLFunctionDecode.NAME, new OSQLFunctionDecode()); + register(OSQLFunctionDifference.NAME, OSQLFunctionDifference.class); + register(OSQLFunctionSymmetricDifference.NAME, OSQLFunctionSymmetricDifference.class); + register(OSQLFunctionDistance.NAME, new OSQLFunctionDistance()); + register(OSQLFunctionDistinct.NAME, OSQLFunctionDistinct.class); + register(OSQLFunctionDocument.NAME, OSQLFunctionDocument.class); + register(OSQLFunctionEncode.NAME, new OSQLFunctionEncode()); + register(OSQLFunctionEval.NAME, OSQLFunctionEval.class); + register(OSQLFunctionFirst.NAME, new OSQLFunctionFirst()); + register(OSQLFunctionFormat.NAME, new OSQLFunctionFormat()); + register(OSQLFunctionTraversedEdge.NAME, OSQLFunctionTraversedEdge.class); + register(OSQLFunctionTraversedElement.NAME, OSQLFunctionTraversedElement.class); + register(OSQLFunctionTraversedVertex.NAME, OSQLFunctionTraversedVertex.class); + register(OSQLFunctionIf.NAME, new OSQLFunctionIf()); + register(OSQLFunctionIfNull.NAME, new OSQLFunctionIfNull()); + register(OSQLFunctionIntersect.NAME, OSQLFunctionIntersect.class); + register(OSQLFunctionLast.NAME, new OSQLFunctionLast()); + register(OSQLFunctionList.NAME, OSQLFunctionList.class); + register(OSQLFunctionMap.NAME, OSQLFunctionMap.class); + register(OSQLFunctionMax.NAME, OSQLFunctionMax.class); + register(OSQLFunctionMin.NAME, OSQLFunctionMin.class); + register(OSQLFunctionSet.NAME, OSQLFunctionSet.class); + register(OSQLFunctionSysdate.NAME, OSQLFunctionSysdate.class); + register(OSQLFunctionSum.NAME, OSQLFunctionSum.class); + register(OSQLFunctionUnionAll.NAME, OSQLFunctionUnionAll.class); + register(OSQLFunctionMode.NAME, OSQLFunctionMode.class); + register(OSQLFunctionPercentile.NAME, OSQLFunctionPercentile.class); + register(OSQLFunctionMedian.NAME, OSQLFunctionMedian.class); + register(OSQLFunctionVariance.NAME, OSQLFunctionVariance.class); + register(OSQLFunctionStandardDeviation.NAME, OSQLFunctionStandardDeviation.class); + register(OSQLFunctionUUID.NAME, OSQLFunctionUUID.class); + register(OSQLFunctionConcat.NAME, OSQLFunctionConcat.class); + register(OSQLFunctionDecimal.NAME, OSQLFunctionDecimal.class); + register(OSQLFunctionSequence.NAME, new OSQLFunctionSequence()); + register(OSQLFunctionAbsoluteValue.NAME, OSQLFunctionAbsoluteValue.class); + } + + public static void register(final String iName, final Object iImplementation) { + FUNCTIONS.put(iName.toLowerCase(Locale.ENGLISH), iImplementation); + } + + @Override + public Set getFunctionNames() { + return FUNCTIONS.keySet(); + } + + @Override + public boolean hasFunction(final String name) { + return FUNCTIONS.containsKey(name); + } + + @Override + public OSQLFunction createFunction(final String name) { + final Object obj = FUNCTIONS.get(name); + + if (obj == null) + throw new OCommandExecutionException("Unknown function name :" + name); + + if (obj instanceof OSQLFunction) + return (OSQLFunction) obj; + else { + // it's a class + final Class clazz = (Class) obj; + try { + return (OSQLFunction) clazz.newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Error in creation of function " + name + + "(). Probably there is not an empty constructor or the constructor generates errors"), e); + } + } + + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OIndexableSQLFunction.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OIndexableSQLFunction.java new file mode 100644 index 00000000000..c0b056fe5bb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OIndexableSQLFunction.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ +package com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.parser.OBinaryCompareOperator; +import com.orientechnologies.orient.core.sql.parser.OExpression; +import com.orientechnologies.orient.core.sql.parser.OFromClause; + +/** + * + * This interface represents SQL functions whose implementation can rely on an index. If used in a WHERE condition, this kind of + * function can be invoked to retrieve target records from an underlying structure, like an index + * + * @author Luigi Dell'Aquila + */ +public interface OIndexableSQLFunction extends OSQLFunction { + + /** + * returns all the entries belonging to the target that match the binary condition where this function appears + * @param target the query target + * @param operator the operator after the function, eg. in select from Foo where myFunction(name) > 4 the operator is > + * @param rightValue the value that has to be compared to the function result, eg. in select from Foo where myFunction(name) > 4 the right value is 4 + * @param ctx the command context for this query + * @param args the function arguments, eg. in select from Foo where myFunction(name) > 4 the arguments are [name] + * @return an iterable of records that match the condition; null means that the execution could not be performed for some reason. + */ + public Iterable searchFromTarget(OFromClause target, OBinaryCompareOperator operator, Object rightValue, OCommandContext ctx, + OExpression... args); + + /** + * estimates the number of entries returned by searchFromTarget() with these parameters + * @param target the query target + * @param operator the operator after the function, eg. in select from Foo where myFunction(name) > 4 the operator is > + * @param rightValue the value that has to be compared to the function result, eg. in select from Foo where myFunction(name) > 4 the right value is 4 + * @param ctx the command context for this query + * @param args the function arguments, eg. in select from Foo where myFunction(name) > 4 the arguments are [name] + * @return an estimantion of how many entries will be returned by searchFromTarget() with these parameters, -1 if the estimation cannot be done + */ + public long estimate(OFromClause target, OBinaryCompareOperator operator, Object rightValue, OCommandContext ctx, + OExpression... args); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunction.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunction.java new file mode 100644 index 00000000000..f39ec855612 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunction.java @@ -0,0 +1,153 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.List; + +/** + * Interface that defines a SQL Function. Functions can be state-less if registered as instance, or state-full when registered as + * class. State-less function are reused across queries, so don't keep any run-time information inside of it. State-full function, + * instead, stores Implement it and register it with: OSQLParser.getInstance().registerFunction() to being used by the + * SQL engine. + * + * ??? could it be possible to have a small piece of code here showing where to register a function using services ??? + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public interface OSQLFunction { + + /** + * Process a record. + * + * + * @param iThis + * @param iCurrentRecord + * : current record + * @param iCurrentResult + * TODO + * @param iParams + * : function parameters, number is ensured to be within minParams and maxParams. + * @param iContext + * : object calling this function + * @return function result, can be null. Special cases : can be null if function aggregate results, can be null if function filter + * results : this mean result is excluded + */ + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, OCommandContext iContext); + + /** + * Configure the function. + * + * @param configuredParameters + */ + public void config(Object[] configuredParameters); + + /** + * A function can make calculation on several records before returning a result. + *

          + * Example of such function : sum, count, max, min ... + *

          + * The final result of the aggregation is obtain by calling {@link #getResult() } + * + * @return true if function aggregate results + */ + public boolean aggregateResults(); + + /** + * A function can act both as transformation or filtering records. If the function may reduce the number final records than it + * must return true. + *

          + * Function should return null for the + * {@linkplain #execute(Object, OIdentifiable, Object, Object[], OCommandContext) + * execute} method if the record must be excluded. + * + * @return true if the function acts as a record filter. + */ + public boolean filterResult(); + + /** + * Function name, the name is used by the sql parser to identify a call this function. + * + * @return String , function name, never null or empty. + */ + public String getName(); + + /** + * Minimum number of parameter this function must have. + * + * @return minimum number of parameters + */ + public int getMinParams(); + + /** + * Maximum number of parameter this function can handle. + * + * @return maximum number of parameters ??? -1 , negative or Integer.MAX_VALUE for unlimited ??? + */ + public int getMaxParams(); + + /** + * Returns a convinient SQL String representation of the function. + *

          + * Example : + * + *

          +	 *  myFunction( param1, param2, [optionalParam3])
          +	 * 
          + * + * This text will be used in exception messages. + * + * @return String , never null. + */ + public String getSyntax(); + + /** + * Only called when function aggregates results after all records have been passed to the function. + * + * @return Aggregation result + */ + public Object getResult(); + + /** + * Called by OCommandExecutor, given parameter is the number of results. ??? strange ??? + * + * @param iResult + */ + public void setResult(Object iResult); + + /** + * This method correspond to distributed query execution + * + * @return {@code true} if results that comes from different nodes need to be merged to obtain valid one, {@code false} otherwise + */ + public boolean shouldMergeDistributedResult(); + + /** + * This method correspond to distributed query execution + * + * @param resultsToMerge + * is the results that comes from different nodes + * @return is the valid merged result + */ + public Object mergeDistributedResult(List resultsToMerge); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionAbstract.java new file mode 100644 index 00000000000..8e35938b562 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionAbstract.java @@ -0,0 +1,107 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.OScenarioThreadLocal; +import com.orientechnologies.orient.core.storage.OAutoshardedStorage; + +import java.util.List; + +/** + * Abstract class to extend to build Custom SQL Functions. Extend it and register it with: + * OSQLParser.getInstance().registerStatelessFunction() or + * OSQLParser.getInstance().registerStatefullFunction() to being used by the SQL engine. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSQLFunctionAbstract implements OSQLFunction { + protected String name; + protected int minParams; + protected int maxParams; + + public OSQLFunctionAbstract(final String iName, final int iMinParams, final int iMaxParams) { + this.name = iName; + this.minParams = iMinParams; + this.maxParams = iMaxParams; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getMinParams() { + return minParams; + } + + @Override + public int getMaxParams() { + return maxParams; + } + + @Override + public String toString() { + return name + "()"; + } + + @Override + public void config(final Object[] iConfiguredParameters) { + } + + @Override + public boolean aggregateResults() { + return false; + } + + @Override + public boolean filterResult() { + return false; + } + + @Override + public Object getResult() { + return null; + } + + @Override + public void setResult(final Object iResult) { + } + + @Override + public boolean shouldMergeDistributedResult() { + return false; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + throw new IllegalStateException("By default SQL function execution result cannot be merged"); + } + + protected boolean returnDistributedResult() { + return OScenarioThreadLocal.INSTANCE.isRunModeDistributed(); + } + + protected String getDistributedStorageId() { + return ((OAutoshardedStorage) ODatabaseRecordThreadLocal.INSTANCE.get().getStorage()).getStorageId(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionConfigurableAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionConfigurableAbstract.java new file mode 100644 index 00000000000..e2f21214c37 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionConfigurableAbstract.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions; + +/** + * Abstract class to extend to build Custom SQL Functions that saves the configured parameters. Extend it and register it with: + * OSQLParser.getInstance().registerStatelessFunction() or + * OSQLParser.getInstance().registerStatefullFunction() to being used by the SQL engine. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSQLFunctionConfigurableAbstract extends OSQLFunctionAbstract { + protected Object[] configuredParameters; + + protected OSQLFunctionConfigurableAbstract(final String iName, final int iMinParams, final int iMaxParams) { + super(iName, iMinParams, iMaxParams); + } + + @Override + public void config(final Object[] iConfiguredParameters) { + configuredParameters = iConfiguredParameters; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(name); + buffer.append('('); + if (configuredParameters != null) { + for (int i = 0; i < configuredParameters.length; ++i) { + if (i > 0) + buffer.append(','); + buffer.append(configuredParameters[i]); + } + } + buffer.append(')'); + return buffer.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFactory.java new file mode 100644 index 00000000000..3b85c4f948d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions; + +import java.util.Set; + +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public interface OSQLFunctionFactory { + + boolean hasFunction(String iName); + + /** + * @return Set of supported function names of this factory + */ + Set getFunctionNames(); + + /** + * Create function for the given name. returned function may be a new instance each time or a constant. + * + * @param name + * @return OSQLFunction : created function + * @throws OCommandExecutionException + * : when function creation fail + */ + OSQLFunction createFunction(String name) throws OCommandExecutionException; + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFiltered.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFiltered.java new file mode 100644 index 00000000000..965fab05936 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionFiltered.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ + +package com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * @author Luigi Dell'Aquila + */ +public interface OSQLFunctionFiltered extends OSQLFunction { + + /** + * Process a record. + * + * + * @param iThis + * @param iCurrentRecord + * : current record + * @param iCurrentResult + * TODO + * @param iParams + * : function parameters, number is ensured to be within minParams and maxParams. + * @param iPossibleResults + * : a set of possible results (the function will return, as a result, only items contained in this collection) + * @param iContext + * : object calling this function + * @return function result, can be null. Special cases : can be null if function aggregate results, can be null if function filter + * results : this mean result is excluded + */ + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, + Iterable iPossibleResults, OCommandContext iContext); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionRuntime.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionRuntime.java new file mode 100644 index 00000000000..bc4706b0ba7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/OSQLFunctionRuntime.java @@ -0,0 +1,219 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandExecutorNotFoundException; +import com.orientechnologies.orient.core.db.record.OAutoConvertToRecord; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemAbstract; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; + +import java.util.List; + +/** + * Wraps function managing the binding of parameters. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionRuntime extends OSQLFilterItemAbstract { + + public OSQLFunction function; + public Object[] configuredParameters; + public Object[] runtimeParameters; + + public OSQLFunctionRuntime(final OBaseParser iQueryToParse, final String iText) { + super(iQueryToParse, iText); + } + + public OSQLFunctionRuntime(final OSQLFunction iFunction) { + function = iFunction; + } + + public boolean aggregateResults() { + return function.aggregateResults(); + } + + public boolean filterResult() { + return function.filterResult(); + } + + /** + * Execute a function. + * + * @param iCurrentRecord + * Current record + * @param iCurrentResult + * TODO + * @param iContext + * @return + */ + public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, + final OCommandContext iContext) { + // RESOLVE VALUES USING THE CURRENT RECORD + for (int i = 0; i < configuredParameters.length; ++i) { + runtimeParameters[i] = configuredParameters[i]; + + if (configuredParameters[i] instanceof OSQLFilterItemField) { + runtimeParameters[i] = ((OSQLFilterItemField) configuredParameters[i]).getValue(iCurrentRecord, iCurrentResult, iContext); + } else if (configuredParameters[i] instanceof OSQLFunctionRuntime) + runtimeParameters[i] = ((OSQLFunctionRuntime) configuredParameters[i]).execute(iThis, iCurrentRecord, iCurrentResult, + iContext); + else if (configuredParameters[i] instanceof OSQLFilterItemVariable) { + runtimeParameters[i] = ((OSQLFilterItemVariable) configuredParameters[i]) + .getValue(iCurrentRecord, iCurrentResult, iContext); + } else if (configuredParameters[i] instanceof OCommandSQL) { + try { + runtimeParameters[i] = ((OCommandSQL) configuredParameters[i]).setContext(iContext).execute(); + } catch (OCommandExecutorNotFoundException e) { + // TRY WITH SIMPLE CONDITION + final String text = ((OCommandSQL) configuredParameters[i]).getText(); + final OSQLPredicate pred = new OSQLPredicate(text); + runtimeParameters[i] = pred.evaluate(iCurrentRecord instanceof ORecord ? (ORecord) iCurrentRecord : null, + (ODocument) iCurrentResult, iContext); + // REPLACE ORIGINAL PARAM + configuredParameters[i] = pred; + + } + } else if (configuredParameters[i] instanceof OSQLPredicate) + runtimeParameters[i] = ((OSQLPredicate) configuredParameters[i]).evaluate(iCurrentRecord.getRecord(), + (iCurrentRecord instanceof ODocument ? (ODocument) iCurrentResult : null), iContext); + else if (configuredParameters[i] instanceof String) { + if (configuredParameters[i].toString().startsWith("\"") || configuredParameters[i].toString().startsWith("'")) + runtimeParameters[i] = OIOUtils.getStringContent(configuredParameters[i]); + } + } + + if (function.getMaxParams() == -1 || function.getMaxParams() > 0) { + if (runtimeParameters.length < function.getMinParams() + || (function.getMaxParams() > -1 && runtimeParameters.length > function.getMaxParams())) + throw new OCommandExecutionException("Syntax error: function '" + + function.getName() + + "' needs " + + (function.getMinParams() == function.getMaxParams() ? function.getMinParams() : function.getMinParams() + "-" + + function.getMaxParams()) + " argument(s) while has been received " + runtimeParameters.length); + } + + final Object functionResult = function.execute(iThis, iCurrentRecord, iCurrentResult, runtimeParameters, iContext); + + if (functionResult instanceof OAutoConvertToRecord) + // FORCE AVOIDING TO CONVERT IN RECORD + ((OAutoConvertToRecord) functionResult).setAutoConvertToRecord(false); + + return transformValue(iCurrentRecord, iContext, functionResult); + } + + public Object getResult() { + return transformValue(null, null, function.getResult()); + } + + public void setResult(final Object iValue) { + function.setResult(iValue); + } + + @Override + public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContext) { + final ODocument current = iRecord != null ? (ODocument) iRecord.getRecord() : null; + return execute(current, current, null, iContext); + } + + @Override + public String getRoot() { + return function.getName(); + } + + public OSQLFunctionRuntime setParameters(final Object[] iParameters, final boolean iEvaluate) { + this.configuredParameters = new Object[iParameters.length]; + for (int i = 0; i < iParameters.length; ++i) { + this.configuredParameters[i] = iParameters[i]; + + if (iEvaluate) + if (iParameters[i] != null) { + if (iParameters[i] instanceof String) { + final Object v = OSQLHelper.parseValue(null, null, iParameters[i].toString(), null); + if (v == OSQLHelper.VALUE_NOT_PARSED + || (v != null && OMultiValue.isMultiValue(v) && OMultiValue.getFirstValue(v) == OSQLHelper.VALUE_NOT_PARSED)) + continue; + + configuredParameters[i] = v; + } + } else + this.configuredParameters[i] = null; + } + + function.config(configuredParameters); + + // COPY STATIC VALUES + this.runtimeParameters = new Object[configuredParameters.length]; + for (int i = 0; i < configuredParameters.length; ++i) { + if (!(configuredParameters[i] instanceof OSQLFilterItemField) && !(configuredParameters[i] instanceof OSQLFunctionRuntime)) + runtimeParameters[i] = configuredParameters[i]; + } + + return this; + } + + public OSQLFunction getFunction() { + return function; + } + + public Object[] getConfiguredParameters() { + return configuredParameters; + } + + public Object[] getRuntimeParameters() { + return runtimeParameters; + } + + @Override + protected void setRoot(final OBaseParser iQueryToParse, final String iText) { + final int beginParenthesis = iText.indexOf('('); + + // SEARCH FOR THE FUNCTION + final String funcName = iText.substring(0, beginParenthesis); + + final List funcParamsText = OStringSerializerHelper.getParameters(iText); + + function = OSQLEngine.getInstance().getFunction(funcName); + if (function == null) + throw new OCommandSQLParsingException("Unknown function " + funcName + "()"); + + // PARSE PARAMETERS + this.configuredParameters = new Object[funcParamsText.size()]; + for (int i = 0; i < funcParamsText.size(); ++i) + this.configuredParameters[i] = funcParamsText.get(i); + + setParameters(configuredParameters, true); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDifference.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDifference.java new file mode 100644 index 00000000000..f240546a283 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDifference.java @@ -0,0 +1,78 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * This operator can work inline. Returns the DIFFERENCE between the collections received as parameters. Works also with no + * collection values. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionDifference extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "difference"; + + public OSQLFunctionDifference() { + super(NAME, 2, -1); + } + + @SuppressWarnings("unchecked") + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams[0] == null) + return null; + + // IN-LINE MODE (STATELESS) + final Set result = new HashSet(); + + boolean first = true; + for (Object iParameter : iParams) { + if (first) { + if (iParameter instanceof Collection) { + result.addAll((Collection) iParameter); + } else { + result.add(iParameter); + } + } else { + if (iParameter instanceof Collection) { + result.removeAll((Collection) iParameter); + } else { + result.remove(iParameter); + } + } + + first = false; + } + + return result; + + } + + public String getSyntax() { + return "difference(, [, context = new LinkedHashSet(); + + public OSQLFunctionDistinct() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + final Object value = iParams[0]; + + if (value != null && !context.contains(value)) { + context.add(value); + return value; + } + + return null; + } + + @Override + public boolean filterResult() { + return true; + } + + public String getSyntax() { + return "distinct()"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDocument.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDocument.java new file mode 100644 index 00000000000..0b02c9c2154 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionDocument.java @@ -0,0 +1,126 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This operator add an entry in a map. The entry is composed by a key and a value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionDocument extends OSQLFunctionMultiValueAbstract { + public static final String NAME = "document"; + + public OSQLFunctionDocument() { + super(NAME, 1, -1); + } + + @SuppressWarnings("unchecked") + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + if (iParams.length > 2) + // IN LINE MODE + context = new ODocument(); + + if (iParams.length == 1) { + if (iParams[0] instanceof ODocument) + // INSERT EVERY DOCUMENT FIELD + context.merge((ODocument) iParams[0], true, false); + else if (iParams[0] instanceof Map) + // INSERT EVERY SINGLE COLLECTION ITEM + context.fields((Map) iParams[0]); + else + throw new IllegalArgumentException("Map function: expected a map or pairs of parameters as key, value"); + } else if (iParams.length % 2 != 0) + throw new IllegalArgumentException("Map function: expected a map or pairs of parameters as key, value"); + else + for (int i = 0; i < iParams.length; i += 2) { + final String key = iParams[i].toString(); + final Object value = iParams[i + 1]; + + if (value != null) { + if (iParams.length <= 2 && context == null) + // AGGREGATION MODE (STATEFULL) + context = new ODocument(); + + context.field(key, value); + } + } + + return prepareResult(context); + } + + public String getSyntax() { + return "document(|[,]*)"; + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return configuredParameters.length <= 2; + } + + @Override + public ODocument getResult() { + final ODocument res = context; + context = null; + return prepareResult(res); + } + + protected ODocument prepareResult(ODocument res) { + if (returnDistributedResult()) { + final ODocument doc = new ODocument(); + doc.field("node", getDistributedStorageId()); + doc.field("context", res); + return doc; + } else { + return res; + } + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + final Map> chunks = new HashMap>(); + for (Object iParameter : resultsToMerge) { + final Map container = (Map) ((Map) iParameter).get("doc"); + chunks.put((String) container.get("node"), (Map) container.get("context")); + } + final Map result = new HashMap(); + for (Map chunk : chunks.values()) { + result.putAll(chunk); + } + return result; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionFirst.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionFirst.java new file mode 100644 index 00000000000..22d0d051070 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionFirst.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Extract the first item of multi values (arrays, collections and maps) or return the same value for non multi-value types. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionFirst extends OSQLFunctionConfigurableAbstract { + public static final String NAME = "first"; + + public OSQLFunctionFirst() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + Object value = iParams[0]; + + if (value instanceof OSQLFilterItem) + value = ((OSQLFilterItem) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + if (OMultiValue.isMultiValue(value)) + value = OMultiValue.getFirstValue(value); + + return value; + } + + public String getSyntax() { + return "first()"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionIntersect.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionIntersect.java new file mode 100644 index 00000000000..72f523f1ae3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionIntersect.java @@ -0,0 +1,147 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.util.OSupportsContains; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ridbag.ORidBag; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable; + +import java.util.*; + +/** + * This operator can work as aggregate or inline. If only one argument is passed than aggregates, otherwise executes, and returns, + * the INTERSECTION of the collections received as parameters. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLFunctionIntersect extends OSQLFunctionMultiValueAbstract { + public static final String NAME = "intersect"; + + public OSQLFunctionIntersect() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + Object value = iParams[0]; + + if (value instanceof OSQLFilterItemVariable) + value = ((OSQLFilterItemVariable) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + if (value == null) + return Collections.emptySet(); + + if (iParams.length == 1) { + // AGGREGATION MODE (STATEFUL) + if (context == null) { + // ADD ALL THE ITEMS OF THE FIRST COLLECTION + if (value instanceof Collection) { + context = ((Collection) value).iterator(); + } else if (value instanceof Iterator) { + context = (Iterator) value; + } else { + context = Arrays.asList(value).iterator(); + } + } else { + Iterator contextIterator = null; + if (context instanceof Iterator) { + contextIterator = (Iterator) context; + } else if (OMultiValue.isMultiValue(context)) { + contextIterator = OMultiValue.getMultiValueIterator(context); + } + context = intersectWith(contextIterator, value); + } + return null; + } + + // IN-LINE MODE (STATELESS) + Iterator iterator = OMultiValue.getMultiValueIterator(value, false); + + for (int i = 1; i < iParams.length; ++i) { + value = iParams[i]; + + if (value instanceof OSQLFilterItemVariable) + value = ((OSQLFilterItemVariable) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + if (value != null) { + value = intersectWith(iterator, value); + iterator = OMultiValue.getMultiValueIterator(value, false); + } else { + return new ArrayList().iterator(); + } + } + + return iterator; + } + + @Override + public Object getResult() { + return OMultiValue.toSet(context); + } + + static Collection intersectWith(final Iterator current, Object value) { + final HashSet tempSet = new HashSet(); + + if (!(value instanceof Set) && (!(value instanceof OSupportsContains) || !((OSupportsContains) value).supportsFastContains())) + value = OMultiValue.toSet(value); + + for (Iterator it = current; it.hasNext(); ) { + final Object curr = it.next(); + if (value instanceof ORidBag) { + if (((ORidBag) value).contains((OIdentifiable) curr)) + tempSet.add(curr); + } else if (value instanceof Collection) { + if (((Collection) value).contains(curr)) + tempSet.add(curr); + } else if (value instanceof OSupportsContains) { + if (((OSupportsContains) value).contains(curr)) + tempSet.add(curr); + } + } + + return tempSet; + } + + public String getSyntax() { + return "intersect(*)"; + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + final Collection result = new HashSet(); + if (!resultsToMerge.isEmpty()) { + final Collection items = (Collection) resultsToMerge.get(0); + if (items != null) { + result.addAll(items); + } + } + for (int i = 1; i < resultsToMerge.size(); i++) { + final Collection items = (Collection) resultsToMerge.get(i); + if (items != null) { + result.retainAll(items); + } + } + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionLast.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionLast.java new file mode 100644 index 00000000000..7f7c9dcfc78 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionLast.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Extract the last item of multi values (arrays, collections and maps) or return the same value for non multi-value types. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionLast extends OSQLFunctionConfigurableAbstract { + public static final String NAME = "last"; + + public OSQLFunctionLast() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + Object value = iParams[0]; + + if (value instanceof OSQLFilterItem) + value = ((OSQLFilterItem) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + if (OMultiValue.isMultiValue(value)) + value = OMultiValue.getLastValue(value); + + return value; + } + + public String getSyntax() { + return "last()"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionList.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionList.java new file mode 100644 index 00000000000..68fb445ddef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionList.java @@ -0,0 +1,104 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.*; + +/** + * This operator add an item in a list. The list accepts duplicates. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLFunctionList extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "list"; + + public OSQLFunctionList() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length > 1) + // IN LINE MODE + context = new ArrayList(); + + for (Object value : iParams) { + if (value != null) { + if (iParams.length == 1 && context == null) + // AGGREGATION MODE (STATEFULL) + context = new ArrayList(); + + if (value instanceof Map) + context.add(value); + else + OMultiValue.add(context, value); + } + } + return prepareResult(context); + } + + public String getSyntax() { + return "list(*)"; + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return false; + } + + @Override + public List getResult() { + final List res = context; + context = null; + return prepareResult(res); + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + final Collection result = new HashSet(); + for (Object iParameter : resultsToMerge) { + final Map container = (Map) ((Collection) iParameter).iterator().next(); + result.addAll((Collection) container.get("context")); + } + return result; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + protected List prepareResult(List res) { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("node", getDistributedStorageId()); + doc.put("context", res); + return Collections.singletonList(doc); + } else { + return res; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMap.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMap.java new file mode 100644 index 00000000000..5b4b1f50103 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMap.java @@ -0,0 +1,126 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This operator add an entry in a map. The entry is composed by a key and a value. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionMap extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "map"; + + public OSQLFunctionMap() { + super(NAME, 1, -1); + } + + @SuppressWarnings("unchecked") + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + if (iParams.length > 2) + // IN LINE MODE + context = new HashMap(); + + if (iParams.length == 1) { + if (iParams[0] == null) + return null; + + if (iParams[0] instanceof Map) { + if (context == null) + // AGGREGATION MODE (STATEFULL) + context = new HashMap(); + + // INSERT EVERY SINGLE COLLECTION ITEM + context.putAll((Map) iParams[0]); + } else + throw new IllegalArgumentException("Map function: expected a map or pairs of parameters as key, value"); + } else if (iParams.length % 2 != 0) + throw new IllegalArgumentException("Map function: expected a map or pairs of parameters as key, value"); + else + for (int i = 0; i < iParams.length; i += 2) { + final Object key = iParams[i]; + final Object value = iParams[i + 1]; + + if (value != null) { + if (iParams.length <= 2 && context == null) + // AGGREGATION MODE (STATEFULL) + context = new HashMap(); + + context.put(key, value); + } + } + + return prepareResult(context); + } + + public String getSyntax() { + return "map(|[,]*)"; + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return configuredParameters.length <= 2; + } + + @Override + public Map getResult() { + final Map res = context; + context = null; + return prepareResult(res); + } + + protected Map prepareResult(final Map res) { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("node", getDistributedStorageId()); + doc.put("context", res); + return Collections. singletonMap("doc", doc); + } else { + return res; + } + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(final List resultsToMerge) { + if (returnDistributedResult()) { + final Map result = new HashMap(); + for (Object iParameter : resultsToMerge) { + final Map container = (Map) ((Map) iParameter).get("doc"); + result.putAll((Map) container.get("context")); + } + return result; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMultiValueAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMultiValueAbstract.java new file mode 100644 index 00000000000..d5c8aecf6cf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionMultiValueAbstract.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Abstract class for multi-value based function implementations. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSQLFunctionMultiValueAbstract extends OSQLFunctionConfigurableAbstract { + + protected T context; + + public OSQLFunctionMultiValueAbstract(final String iName, final int iMinParams, final int iMaxParams) { + super(iName, iMinParams, iMaxParams); + } + + @Override + public boolean aggregateResults() { + return configuredParameters.length == 1; + } + + @Override + public T getResult() { + return context; + } + + @Override + public boolean shouldMergeDistributedResult() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSet.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSet.java new file mode 100644 index 00000000000..eaa5494acfd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSet.java @@ -0,0 +1,114 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This operator add an item in a set. The set doesn't accept duplicates, so adding multiple times the same value has no effect: the + * value is contained only once. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionSet extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "set"; + + public OSQLFunctionSet() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length > 1) + // IN LINE MODE + context = new HashSet(); + + for (Object value : iParams) { + if (value != null) { + if (iParams.length == 1 && context == null) + // AGGREGATION MODE (STATEFULL) + context = new HashSet(); + + if (value instanceof ODocument) + context.add(value); + else + OMultiValue.add(context, value); + } + } + + return prepareResult(context); + } + + public String getSyntax() { + return "set(*)"; + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return configuredParameters.length == 1; + } + + @Override + public Set getResult() { + final Set res = context; + context = null; + return prepareResult(res); + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + final Collection result = new HashSet(); + for (Object iParameter : resultsToMerge) { + final Map container = (Map) ((Collection) iParameter).iterator().next(); + result.addAll((Collection) container.get("context")); + } + return result; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + protected Set prepareResult(Set res) { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("node", getDistributedStorageId()); + doc.put("context", context); + return Collections. singleton(doc); + } else { + return res; + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSymmetricDifference.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSymmetricDifference.java new file mode 100644 index 00000000000..78c4564b768 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionSymmetricDifference.java @@ -0,0 +1,145 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This operator can work as aggregate or inline. If only one argument is passed than aggregates, otherwise executes, and returns, + * the SYMMETRIC DIFFERENCE between the collections received as parameters. Works also with no collection values. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionSymmetricDifference extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "symmetricDifference"; + + private Set rejected; + + public OSQLFunctionSymmetricDifference() { + super(NAME, 1, -1); + } + + private static void addItemToResult(Object o, Set accepted, Set rejected) { + if (!accepted.contains(o) && !rejected.contains(o)) { + accepted.add(o); + } else { + accepted.remove(o); + rejected.add(o); + } + } + + private static void addItemsToResult(Collection co, Set accepted, Set rejected) { + for (Object o : co) { + addItemToResult(o, accepted, rejected); + } + } + + @SuppressWarnings("unchecked") + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams[0] == null) + return null; + + Object value = iParams[0]; + + if (iParams.length == 1) { + // AGGREGATION MODE (STATEFUL) + if (context == null) { + context = new HashSet(); + rejected = new HashSet(); + } + if (value instanceof Collection) { + addItemsToResult((Collection) value, context, rejected); + } else { + addItemToResult(value, context, rejected); + } + + return null; + } else { + // IN-LINE MODE (STATELESS) + final Set result = new HashSet(); + final Set rejected = new HashSet(); + + for (Object iParameter : iParams) { + if (iParameter instanceof Collection) { + addItemsToResult((Collection) iParameter, result, rejected); + } else { + addItemToResult(iParameter, result, rejected); + } + } + + return result; + } + } + + @Override + public Set getResult() { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("result", context); + doc.put("rejected", rejected); + return Collections. singleton(doc); + } else { + return super.getResult(); + } + } + + public String getSyntax() { + return "difference(*)"; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + final Set result = new HashSet(); + final Set rejected = new HashSet(); + for (Object item : resultsToMerge) { + rejected.addAll(unwrap(item, "rejected")); + } + for (Object item : resultsToMerge) { + addItemsToResult(unwrap(item, "result"), result, rejected); + } + return result; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + @SuppressWarnings("unchecked") + private Set unwrap(Object obj, String field) { + final Set objAsSet = (Set) obj; + final Map objAsMap = (Map) objAsSet.iterator().next(); + final Set objAsField = (Set) objAsMap.get(field); + return objAsField; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedEdge.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedEdge.java new file mode 100644 index 00000000000..247fcaaa26d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedEdge.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Returns a traversed element from the stack. Use it with SQL traverse only. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionTraversedEdge extends OSQLFunctionTraversedElement { + public static final String NAME = "traversedEdge"; + + public OSQLFunctionTraversedEdge() { + super(NAME); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + return evaluate( iParams, iContext, "E" ); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedElement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedElement.java new file mode 100644 index 00000000000..f3531126440 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedElement.java @@ -0,0 +1,135 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.traverse.OTraverseRecordProcess; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Returns a traversed element from the stack. Use it with SQL traverse only. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionTraversedElement extends OSQLFunctionConfigurableAbstract { + public static final String NAME = "traversedElement"; + + public OSQLFunctionTraversedElement() { + super(NAME, 1, 2); + } + + public OSQLFunctionTraversedElement(final String name) { + super(name, 1, 2); + } + + public boolean aggregateResults() { + return false; + } + + @Override + public Object getResult() { + return null; + } + + @Override + public boolean filterResult() { + return true; + } + + public String getSyntax() { + return getName() + "( [,])"; + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + return evaluate(iParams, iContext, null); + } + + protected Object evaluate(final Object[] iParams, final OCommandContext iContext, final String iClassName) { + final int beginIndex = (Integer) iParams[0]; + final int items = iParams.length > 1 ? (Integer) iParams[1] : 1; + + final ArrayDeque stack = (ArrayDeque) iContext.getVariable("stack"); + if (stack == null) + throw new OCommandExecutionException("Cannot invoke " + getName() + "() against non traverse command"); + + final List result = items > 1 ? new ArrayList(items) : null; + + if (beginIndex < 0) { + int i = -1; + for (Iterator it = stack.iterator(); it.hasNext();) { + final Object o = it.next(); + if (o instanceof OTraverseRecordProcess) { + final OIdentifiable record = ((OTraverseRecordProcess) o).getTarget(); + + if (iClassName == null + || ODocumentInternal.getImmutableSchemaClass((ODocument) record.getRecord()).isSubClassOf(iClassName)) { + if (i <= beginIndex) { + if (items == 1) + return record; + else { + result.add(record); + if (result.size() >= items) + break; + } + } + i--; + } + } + } + } else { + int i = 0; + for (Iterator it = stack.descendingIterator(); it.hasNext();) { + final Object o = it.next(); + if (o instanceof OTraverseRecordProcess) { + final OIdentifiable record = ((OTraverseRecordProcess) o).getTarget(); + + if (iClassName == null + || ODocumentInternal.getImmutableSchemaClass((ODocument) record.getRecord()).isSubClassOf(iClassName)) { + if (i >= beginIndex) { + if (items == 1) + return record; + else { + result.add(record); + if (result.size() >= items) + break; + } + } + i++; + } + } + } + } + + if (items > 0 && result != null && !result.isEmpty()) + return result; + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedVertex.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedVertex.java new file mode 100644 index 00000000000..484543da9ae --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionTraversedVertex.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Returns a traversed element from the stack. Use it with SQL traverse only. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionTraversedVertex extends OSQLFunctionTraversedElement { + public static final String NAME = "traversedVertex"; + + public OSQLFunctionTraversedVertex() { + super(NAME); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + return evaluate( iParams, iContext, "V" ); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionUnionAll.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionUnionAll.java new file mode 100644 index 00000000000..597b499d1d0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLFunctionUnionAll.java @@ -0,0 +1,97 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +/** + * This operator can work as aggregate or inline. If only one argument is passed than aggregates, otherwise executes, and returns, a + * UNION of the collections received as parameters. Works also with no collection values. Does not remove duplication from the + * result. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionUnionAll extends OSQLFunctionMultiValueAbstract> { + public static final String NAME = "unionAll"; + + public OSQLFunctionUnionAll() { + super(NAME, 1, -1); + } + + public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, + final Object[] iParams, OCommandContext iContext) { + if (iParams.length == 1) { + // AGGREGATION MODE (STATEFUL) + Object value = iParams[0]; + if (value != null) { + + if (value instanceof OSQLFilterItemVariable) + value = ((OSQLFilterItemVariable) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + if (context == null) + context = new ArrayList(); + + OMultiValue.add(context, value); + } + + return context; + } else { + // IN-LINE MODE (STATELESS) + final OMultiCollectionIterator result = new OMultiCollectionIterator(); + for (Object value : iParams) { + if (value != null) { + if (value instanceof OSQLFilterItemVariable) + value = ((OSQLFilterItemVariable) value).getValue(iCurrentRecord, iCurrentResult, iContext); + + result.add(value); + } + } + + return result; + } + } + + public String getSyntax() { + return "unionAll(*)"; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + final Collection result = new HashSet(); + for (Object iParameter : resultsToMerge) { + @SuppressWarnings("unchecked") + final Collection items = (Collection) iParameter; + if (items != null) { + result.addAll(items); + } + } + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLMethodMultiValue.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLMethodMultiValue.java new file mode 100755 index 00000000000..075c9f1a538 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/coll/OSQLMethodMultiValue.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.coll; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.util.ArrayList; +import java.util.List; + +/** + * Works against multi value objects like collections, maps and arrays. + * + * @author Luca Garulli + */ +public class OSQLMethodMultiValue extends OAbstractSQLMethod { + + public static final String NAME = "multivalue"; + + public OSQLMethodMultiValue() { + super(NAME, 1, -1); + } + + @Override + public String getSyntax() { + return "multivalue()"; + + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) { + return null; + } + + if (iParams.length == 1 && !OMultiValue.isMultiValue(iParams[0])) { + return ODocumentHelper.getFieldValue(iThis, iParams[0].toString(), iContext); + } + + // MULTI VALUES + final List list = new ArrayList(); + for (int i = 0; i < iParams.length; ++i) { + if (OMultiValue.isMultiValue(iParams[i])) { + for (Object o : OMultiValue.getMultiValueIterable(iParams[i], false)) { + list.add(ODocumentHelper.getFieldValue(iThis, o.toString(), iContext)); + } + } else { + list.add(ODocumentHelper.getFieldValue(iThis, iParams[i].toString(), iContext)); + } + } + + if (list.size() == 1) { + return list.get(0); + } + + return list; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDate.java new file mode 100644 index 00000000000..d67745b2020 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDate.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.conversion; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.text.ParseException; +import java.util.Date; + +/** + * Transforms a value to date. If the conversion is not possible, null is returned. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsDate extends OAbstractSQLMethod { + + public static final String NAME = "asdate"; + + public OSQLMethodAsDate() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "asDate()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis != null) { + if (iThis instanceof Date) { + return iThis; + } else if (iThis instanceof Number) { + return new Date(((Number) iThis).longValue()); + } else { + try { + return ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateFormatInstance().parse(iThis.toString()); + } catch (ParseException e) { + // IGNORE IT: RETURN NULL + } + } + } + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDateTime.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDateTime.java new file mode 100644 index 00000000000..d0e36ba2a38 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDateTime.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.conversion; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.text.ParseException; +import java.util.Date; + +/** + * Transforms a value to datetime. If the conversion is not possible, null is returned. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsDateTime extends OAbstractSQLMethod { + + public static final String NAME = "asdatetime"; + + public OSQLMethodAsDateTime() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "asDatetime()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis != null) { + if (iThis instanceof Date) { + return iThis; + } else if (iThis instanceof Number) { + return new Date(((Number) iThis).longValue()); + } else { + try { + return ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateTimeFormatInstance().parse(iThis.toString()); + } catch (ParseException e) { + // IGNORE IT: RETURN NULL + } + } + } + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDecimal.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDecimal.java new file mode 100644 index 00000000000..d2e770d08f1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodAsDecimal.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.conversion; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.math.BigDecimal; + +/** + * Transforms a value to decimal. If the conversion is not possible, null is returned. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsDecimal extends OAbstractSQLMethod { + + public static final String NAME = "asdecimal"; + + public OSQLMethodAsDecimal() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "asDecimal()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + return iThis != null ? new BigDecimal(iThis.toString().trim()) : null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodConvert.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodConvert.java new file mode 100755 index 00000000000..7cde18ce878 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/OSQLMethodConvert.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.conversion; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.util.Locale; + +/** + * Converts a value to another type in Java or OrientDB's supported types. + * + * @author Luca Garulli + */ +public class OSQLMethodConvert extends OAbstractSQLMethod { + + public static final String NAME = "convert"; + + public OSQLMethodConvert() { + super(NAME, 1, 1); + } + + @Override + public String getSyntax() { + return "convert()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) { + return null; + } + + final String destType = iParams[0].toString(); + + if (destType.contains(".")) { + try { + return OType.convert(iThis, Class.forName(destType)); + } catch (ClassNotFoundException e) { + } + } else { + final OType orientType = OType.valueOf(destType.toUpperCase(Locale.ENGLISH)); + if (orientType != null) { + return OType.convert(iThis, orientType.getDefaultJavaType()); + } + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/package-info.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/package-info.java new file mode 100644 index 00000000000..72316c4f3f8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/conversion/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2013 Orient Technologies LTD (info--at--orientechnologies.com) + * + * Licensed 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. + */ +/** + * @author Luca Garulli + * + */ +package com.orientechnologies.orient.core.sql.functions.conversion; \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/geo/OSQLFunctionDistance.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/geo/OSQLFunctionDistance.java new file mode 100644 index 00000000000..c8d89f82284 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/geo/OSQLFunctionDistance.java @@ -0,0 +1,89 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.geo; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Haversine formula to compute the distance between 2 gro points. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionDistance extends OSQLFunctionAbstract { + public static final String NAME = "distance"; + + private final static double EARTH_RADIUS = 6371; + + public OSQLFunctionDistance() { + super(NAME, 4, 5); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + try { + double distance; + + final double[] values = new double[4]; + + for (int i = 0; i < iParams.length && i < 4; ++i) { + if (iParams[i] == null) + return null; + + values[i] = ((Double) OType.convert(iParams[i], Double.class)).doubleValue(); + } + + final double deltaLat = Math.toRadians(values[2] - values[0]); + final double deltaLon = Math.toRadians(values[3] - values[1]); + + final double a = Math.pow(Math.sin(deltaLat / 2), 2) + Math.cos(Math.toRadians(values[0])) + * Math.cos(Math.toRadians(values[2])) * Math.pow(Math.sin(deltaLon / 2), 2); + distance = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * EARTH_RADIUS; + + if (iParams.length > 4) { + final String unit = iParams[4].toString(); + if (unit.equalsIgnoreCase("km")) + // ALREADY IN KM + ; + else if (unit.equalsIgnoreCase("mi")) + // MILES + distance *= 0.621371192; + else if (unit.equalsIgnoreCase("nmi")) + // NAUTICAL MILES + distance *= 0.539956803; + else + throw new IllegalArgumentException("Unsupported unit '" + unit + "'. Use km, mi and nmi. Default is km."); + } + + return distance; + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + return null; + } + } + + public String getSyntax() { + return "distance(,,,[,])"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAbsoluteValue.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAbsoluteValue.java new file mode 100644 index 00000000000..68eab56956e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAbsoluteValue.java @@ -0,0 +1,90 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +/** + * Evaluates the absolute value for numeric types. The argument must be a + * BigDecimal, BigInteger, Integer, Long, Double or a Float, or null. If + * null is passed in the result will be null. Otherwise the result will + * be the mathematical absolute value of the argument passed in and will be + * of the same type that was passed in. + * + * @author Michael MacFadden + */ +public class OSQLFunctionAbsoluteValue extends OSQLFunctionMathAbstract { + public static final String NAME = "abs"; + private Object result; + + public OSQLFunctionAbsoluteValue() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iRecord, final Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + Object inputValue = iParams[0]; + + if (inputValue == null) { + result = null; + } else if (inputValue instanceof BigDecimal) { + result = ((BigDecimal) inputValue).abs(); + } else if (inputValue instanceof BigInteger) { + result = ((BigInteger) inputValue).abs(); + }else if (inputValue instanceof Integer) { + result = Math.abs((Integer)inputValue); + } else if (inputValue instanceof Long) { + result = Math.abs((Long) inputValue); + } else if (inputValue instanceof Short) { + result = (short)Math.abs((Short) inputValue); + } else if (inputValue instanceof Double) { + result = Math.abs((Double) inputValue); + } else if (inputValue instanceof Float) { + result = Math.abs((Float) inputValue); + } else { + throw new IllegalArgumentException("Argument to absolute value must be a number."); + } + + return getResult(); + } + + public boolean aggregateResults() { + return false; + } + + public String getSyntax() { + return "abs()"; + } + + @Override + public Object getResult() { + return result; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAverage.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAverage.java new file mode 100644 index 00000000000..c880d3ad157 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionAverage.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Compute the average value for a field. Uses the context to save the last average number. When different Number class are used, + * take the class with most precision. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionAverage extends OSQLFunctionMathAbstract { + public static final String NAME = "avg"; + + private Number sum; + private int total = 0; + + public OSQLFunctionAverage() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length == 1) { + if (iParams[0] instanceof Number) + sum((Number) iParams[0]); + else if (OMultiValue.isMultiValue(iParams[0])) + for (Object n : OMultiValue.getMultiValueIterable(iParams[0])) + sum((Number) n); + + } else { + sum = null; + for (int i = 0; i < iParams.length; ++i) + sum((Number) iParams[i]); + } + + return getResult(); + } + + protected void sum(Number value) { + if (value != null) { + total++; + if (sum == null) + // FIRST TIME + sum = value; + else + sum = OType.increment(sum, value); + } + } + + public String getSyntax() { + return "avg( [,*])"; + } + + @Override + public Object getResult() { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("sum", sum); + doc.put("total", total); + return doc; + } else { + return computeAverage(sum, total); + } + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(final List resultsToMerge) { + if (returnDistributedResult()) { + Number dSum = null; + int dTotal = 0; + for (Object iParameter : resultsToMerge) { + final Map item = (Map) iParameter; + if (dSum == null) + dSum = (Number) item.get("sum"); + else + dSum = OType.increment(dSum, (Number) item.get("sum")); + + dTotal += (Integer) item.get("total"); + } + + return computeAverage(dSum, dTotal); + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + @Override + public boolean aggregateResults() { + return configuredParameters.length == 1; + } + + private Object computeAverage(Number iSum, int iTotal) { + if (iSum instanceof Integer) + return iSum.intValue() / iTotal; + else if (iSum instanceof Long) + return iSum.longValue() / iTotal; + else if (iSum instanceof Float) + return iSum.floatValue() / iTotal; + else if (iSum instanceof Double) + return iSum.doubleValue() / iTotal; + else if (iSum instanceof BigDecimal) + return ((BigDecimal) iSum).divide(new BigDecimal(iTotal), RoundingMode.HALF_UP); + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionDecimal.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionDecimal.java new file mode 100644 index 00000000000..1e271e93abe --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionDecimal.java @@ -0,0 +1,90 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +/** + * Evaluates a complex expression. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionDecimal extends OSQLFunctionMathAbstract { + public static final String NAME = "decimal"; + private Object result; + + public OSQLFunctionDecimal() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iRecord, final Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + Object inputValue = iParams[0]; + if (inputValue == null) { + result = null; + } + + if (inputValue instanceof BigDecimal) { + result = inputValue; + }else if (inputValue instanceof BigInteger) { + result = new BigDecimal((BigInteger) inputValue); + }else if (inputValue instanceof Integer) { + result = new BigDecimal(((Integer) inputValue)); + }else if (inputValue instanceof Long) { + result = new BigDecimal(((Long) inputValue)); + }else if (inputValue instanceof Number) { + result = new BigDecimal(((Number) inputValue).doubleValue()); + } + + try { + if (inputValue instanceof String) { + result = new BigDecimal((String) inputValue); + } + + } catch (Exception e) { + result = null; + } + return getResult(); + } + + public boolean aggregateResults() { + return false; + } + + public String getSyntax() { + return "decimal()"; + } + + @Override + public Object getResult() { + return result; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionEval.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionEval.java new file mode 100644 index 00000000000..58071f0310e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionEval.java @@ -0,0 +1,77 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; + +import java.util.List; + +/** + * Evaluates a complex expression. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionEval extends OSQLFunctionMathAbstract { + public static final String NAME = "eval"; + + private OSQLPredicate predicate; + + public OSQLFunctionEval() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, final OIdentifiable iRecord, final Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (predicate == null) + predicate = new OSQLPredicate((String) iParams[0].toString()); + + final ODocument currentResult = iCurrentResult instanceof ODocument ? (ODocument) iCurrentResult : null; + try { + return predicate.evaluate(iRecord != null ? iRecord.getRecord() : null, currentResult, iContext); + } catch (ArithmeticException e) { + // DIVISION BY 0 + return 0; + } catch (Exception e) { + return null; + } + } + + public boolean aggregateResults() { + return false; + } + + public String getSyntax() { + return "eval()"; + } + + @Override + public Object getResult() { + return null; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMathAbstract.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMathAbstract.java new file mode 100644 index 00000000000..184db395786 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMathAbstract.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import java.math.BigDecimal; + +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Abstract class for math function. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public abstract class OSQLFunctionMathAbstract extends OSQLFunctionConfigurableAbstract { + + public OSQLFunctionMathAbstract(String iName, int iMinParams, int iMaxParams) { + super(iName, iMinParams, iMaxParams); + } + + protected Number getContextValue(Object iContext, final Class iClass) { + if (iClass != iContext.getClass()) { + // CHANGE TYPE + if (iClass == Long.class) + iContext = new Long(((Number) iContext).longValue()); + else if (iClass == Short.class) + iContext = new Short(((Number) iContext).shortValue()); + else if (iClass == Float.class) + iContext = new Float(((Number) iContext).floatValue()); + else if (iClass == Double.class) + iContext = new Double(((Number) iContext).doubleValue()); + } + + return (Number) iContext; + } + + protected Class getClassWithMorePrecision(final Class iClass1, + final Class iClass2) { + if (iClass1 == iClass2) + return iClass1; + + if (iClass1 == Integer.class + && (iClass2 == Long.class || iClass2 == Float.class || iClass2 == Double.class || iClass2 == BigDecimal.class)) + return iClass2; + else if (iClass1 == Long.class && (iClass2 == Float.class || iClass2 == Double.class || iClass2 == BigDecimal.class)) + return iClass2; + else if (iClass1 == Float.class && (iClass2 == Double.class || iClass2 == BigDecimal.class)) + return iClass2; + + return iClass1; + } + + @Override + public boolean aggregateResults() { + return configuredParameters.length == 1; + } + + @Override + public boolean shouldMergeDistributedResult() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMax.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMax.java new file mode 100644 index 00000000000..f83ca5dd881 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMax.java @@ -0,0 +1,122 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.Collection; +import java.util.List; + +/** + * Compute the maximum value for a field. Uses the context to save the last maximum number. When different Number class are used, + * take the class with most precision. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLFunctionMax extends OSQLFunctionMathAbstract { + public static final String NAME = "max"; + + private Object context; + + public OSQLFunctionMax() { + super(NAME, 1, -1); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + // calculate max value for current record + // consider both collection of parameters and collection in each parameter + Object max = null; + for (Object item : iParams) { + if (item instanceof Collection) { + for (Object subitem : ((Collection) item)) { + if (max == null || subitem != null && ((Comparable) subitem).compareTo(max) > 0) + max = subitem; + } + } else { + if ((item instanceof Number) && (max instanceof Number)) { + Number[] converted = OType.castComparableNumber((Number) item, (Number) max); + item = converted[0]; + max = converted[1]; + } + if (max == null || item != null && ((Comparable) item).compareTo(max) > 0) + max = item; + } + } + + // what to do with the result, for current record, depends on how this function has been invoked + // for an unique result aggregated from all output records + if (aggregateResults() && max != null) { + if (context == null) + // FIRST TIME + context = (Comparable) max; + else { + if (context instanceof Number && max instanceof Number) { + final Number[] casted = OType.castComparableNumber((Number) context, (Number) max); + context = casted[0]; + max = casted[1]; + } + if (((Comparable) context).compareTo((Comparable) max) < 0) + // BIGGER + context = (Comparable) max; + } + + return null; + } + + // for non aggregated results (a result per output record) + return max; + } + + public boolean aggregateResults() { + // LET definitions (contain $current) does not require results aggregation + return ((configuredParameters.length == 1) && !configuredParameters[0].toString().contains("$current")); + } + + public String getSyntax() { + return "max( [,*])"; + } + + @Override + public Object getResult() { + return context; + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + Comparable context = null; + for (Object iParameter : resultsToMerge) { + final Comparable value = (Comparable) iParameter; + + if (context == null) + // FIRST TIME + context = value; + else if (context.compareTo(value) < 0) + // BIGGER + context = value; + } + return context; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMin.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMin.java new file mode 100644 index 00000000000..914e32a1f50 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionMin.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.Collection; +import java.util.List; + +/** + * Compute the minimum value for a field. Uses the context to save the last minimum number. When different Number class are used, + * take the class with most precision. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionMin extends OSQLFunctionMathAbstract { + public static final String NAME = "min"; + + private Object context; + + public OSQLFunctionMin() { + super(NAME, 1, -1); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + // calculate min value for current record + // consider both collection of parameters and collection in each parameter + Object min = null; + for (Object item : iParams) { + if (item instanceof Collection) { + for (Object subitem : ((Collection) item)) { + if (min == null || subitem != null && ((Comparable) subitem).compareTo(min) < 0) + min = subitem; + } + } else { + if ((item instanceof Number) && (min instanceof Number)) { + Number[] converted = OType.castComparableNumber((Number) item, (Number) min); + item = converted[0]; + min = converted[1]; + } + if (min == null || item != null && ((Comparable) item).compareTo(min) < 0) + min = item; + } + } + + // what to do with the result, for current record, depends on how this function has been invoked + // for an unique result aggregated from all output records + if (aggregateResults() && min != null) { + if (context == null) + // FIRST TIME + context = (Comparable) min; + else { + if (context instanceof Number && min instanceof Number) { + final Number[] casted = OType.castComparableNumber((Number) context, (Number) min); + context = casted[0]; + min = casted[1]; + } + + if (((Comparable) context).compareTo((Comparable) min) > 0) + // MINOR + context = (Comparable) min; + } + + return null; + } + + // for non aggregated results (a result per output record) + return min; + } + + public boolean aggregateResults() { + // LET definitions (contain $current) does not require results aggregation + return ((configuredParameters.length == 1) && !configuredParameters[0].toString().contains("$current")); + } + + public String getSyntax() { + return "min( [,*])"; + } + + @Override + public Object getResult() { + return context; + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + Comparable context = null; + for (Object iParameter : resultsToMerge) { + final Comparable value = (Comparable) iParameter; + + if (context == null) + // FIRST TIME + context = value; + else if (context.compareTo(value) > 0) + // BIGGER + context = value; + } + return context; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionSum.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionSum.java new file mode 100644 index 00000000000..cc6347e23ed --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/math/OSQLFunctionSum.java @@ -0,0 +1,100 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.math; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.List; + +/** + * Computes the sum of field. Uses the context to save the last sum number. When different Number class are used, take the class + * with most precision. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLFunctionSum extends OSQLFunctionMathAbstract { + public static final String NAME = "sum"; + + private Number sum; + + public OSQLFunctionSum() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length == 1) { + if (iParams[0] instanceof Number) + sum((Number) iParams[0]); + else if (OMultiValue.isMultiValue(iParams[0])) + for (Object n : OMultiValue.getMultiValueIterable(iParams[0])) + sum((Number) n); + } else { + sum = null; + for (int i = 0; i < iParams.length; ++i) + sum((Number) iParams[i]); + } + return sum; + } + + protected void sum(final Number value) { + if (value != null) { + if (sum == null) + // FIRST TIME + sum = value; + else + sum = OType.increment(sum, value); + } + } + + @Override + public boolean aggregateResults() { + return configuredParameters.length == 1; + } + + public String getSyntax() { + return "sum( [,*])"; + } + + @Override + public Object getResult() { + return sum == null ? 0 : sum; + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + Number sum = null; + for (Object iParameter : resultsToMerge) { + final Number value = (Number) iParameter; + + if (value != null) { + if (sum == null) + // FIRST TIME + sum = value; + else + sum = OType.increment(sum, value); + } + } + return sum; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCoalesce.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCoalesce.java new file mode 100644 index 00000000000..217736e760b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCoalesce.java @@ -0,0 +1,83 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Returns the first field/value not null parameter. if no field/value is not null, returns null. + * + *

          + * Syntax:

          + * + *
          + * coalesce(<field|value>[,<field|value>]*)
          + * 
          + * + *
          + * + *

          + * Examples:

          + * + *
          + * SELECT coalesce('a', 'b') FROM ...
          + *  -> 'a'
          + * 
          + * SELECT coalesce(null, 'b') FROM ...
          + *  -> 'b'
          + * 
          + * SELECT coalesce(null, null, 'c') FROM ...
          + *  -> 'c'
          + * 
          + * SELECT coalesce(null, null) FROM ...
          + *  -> null
          + * 
          + * 
          + * + *
          + * + * @author Claudio Tesoriero + */ + +public class OSQLFunctionCoalesce extends OSQLFunctionAbstract { + public static final String NAME = "coalesce"; + + public OSQLFunctionCoalesce() { + super(NAME, 1, 1000); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, OCommandContext iContext) { + int length = iParams.length; + for (int i = 0; i < length; i++) { + if (iParams[i] != null) + return iParams[i]; + } + return null; + } + + @Override + public String getSyntax() { + return "Returns the first not-null parameter or null if all parameters are null. Syntax: coalesce( [,]*)"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCount.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCount.java new file mode 100644 index 00000000000..a58cb8cb360 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionCount.java @@ -0,0 +1,79 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionMathAbstract; + +import java.util.List; + +/** + * Count the record that contains a field. Use * to indicate the record instead of the field. Uses the context to save the counter + * number. When different Number class are used, take the class with most precision. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionCount extends OSQLFunctionMathAbstract { + public static final String NAME = "count"; + + private long total = 0; + + public OSQLFunctionCount() { + super(NAME, 1, 1); + } + + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams[0] != null) + total++; + + return total; + } + + public boolean aggregateResults() { + return true; + } + + public String getSyntax() { + return "count(|*)"; + } + + @Override + public Object getResult() { + return total; + } + + @Override + public void setResult(final Object iResult) { + total = ((Number) iResult).longValue(); + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + long total = 0; + for (Object iParameter : resultsToMerge) { + final long value = (Long) iParameter; + total += value; + } + return total; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDate.java new file mode 100755 index 00000000000..3027415aaa7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDate.java @@ -0,0 +1,99 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OQueryParsingException; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Builds a date object from the format passed. If no arguments are passed, than the system date is built (like sysdate() function) + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @see OSQLFunctionSysdate + * + */ +public class OSQLFunctionDate extends OSQLFunctionAbstract { + public static final String NAME = "date"; + + private Date date; + private SimpleDateFormat format; + + /** + * Get the date at construction to have the same date for all the iteration. + */ + public OSQLFunctionDate() { + super(NAME, 0, 3); + date = new Date(); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length == 0) + return date; + + if (iParams[0] == null) + return null; + + if (iParams[0] instanceof Number) + return new Date(((Number) iParams[0]).longValue()); + + if (format == null) { + if (iParams.length > 1) { + format = new SimpleDateFormat((String) iParams[1]); + format.setTimeZone(ODateHelper.getDatabaseTimeZone()); + } else + format = ODatabaseRecordThreadLocal.INSTANCE.get().getStorage().getConfiguration().getDateTimeFormatInstance(); + + if (iParams.length == 3) + format.setTimeZone(TimeZone.getTimeZone(iParams[2].toString())); + } + + try { + return format.parse((String) iParams[0]); + } catch (ParseException e) { + throw OException.wrapException(new OQueryParsingException("Error on formatting date '" + iParams[0] + "' using the format: " + + format.toPattern()), e); + } + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return false; + } + + public String getSyntax() { + return "date([] [,] [,])"; + } + + @Override + public Object getResult() { + format = null; + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDecode.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDecode.java new file mode 100755 index 00000000000..3c946841a8c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionDecode.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Geomatys + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Encode a string in various format (only base64 for now) + * + * @author Johann Sorel (Geomatys) + */ +public class OSQLFunctionDecode extends OSQLFunctionAbstract { + + public static final String NAME = "decode"; + + /** + * Get the date at construction to have the same date for all the iteration. + */ + public OSQLFunctionDecode() { + super(NAME, 2, 2); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + final String candidate = iParams[0].toString(); + final String format = iParams[1].toString(); + + if (OSQLFunctionEncode.FORMAT_BASE64.equalsIgnoreCase(format)) { + return OBase64Utils.decode(candidate); + } else { + throw new ODatabaseException("unknowned format :" + format); + } + } + + @Override + public String getSyntax() { + return "decode(, )"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionEncode.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionEncode.java new file mode 100755 index 00000000000..6a08f58d544 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionEncode.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Geomatys + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.OBlob; +import com.orientechnologies.orient.core.serialization.OBase64Utils; +import com.orientechnologies.orient.core.serialization.OSerializableStream; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Encode a string in various format (only base64 for now) + * + * @author Johann Sorel (Geomatys) + */ +public class OSQLFunctionEncode extends OSQLFunctionAbstract { + + public static final String NAME = "encode"; + public static final String FORMAT_BASE64 = "base64"; + + /** + * Get the date at construction to have the same date for all the iteration. + */ + public OSQLFunctionEncode() { + super(NAME, 2, 2); + } + + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + + final Object candidate = iParams[0]; + final String format = iParams[1].toString(); + + byte[] data = null; + if (candidate instanceof byte[]) { + data = (byte[]) candidate; + } else if (candidate instanceof ORecordId) { + final ORecord rec = ((ORecordId) candidate).getRecord(); + if (rec instanceof OBlob) { + data = ((OBlob) rec).toStream(); + } + } else if (candidate instanceof OSerializableStream) { + data = ((OSerializableStream) candidate).toStream(); + } + + if (data == null) { + return null; + } + + if (FORMAT_BASE64.equalsIgnoreCase(format)) { + return OBase64Utils.encodeBytes(data); + } else { + throw new ODatabaseException("unknowned format :" + format); + } + } + + @Override + public String getSyntax() { + return "encode(, )"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionFormat.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionFormat.java new file mode 100644 index 00000000000..d6317bfbfa4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionFormat.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Formats content. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionFormat extends OSQLFunctionAbstract { + public static final String NAME = "format"; + + public OSQLFunctionFormat() { + super(NAME, 2, -1); + } + + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + final Object[] args = new Object[iParams.length - 1]; + + for (int i = 0; i < args.length; ++i) + args[i] = iParams[i + 1]; + + return String.format((String) iParams[0], args); + } + + public String getSyntax() { + return "format(, [,]*)"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIf.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIf.java new file mode 100644 index 00000000000..770de587692 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIf.java @@ -0,0 +1,88 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Returns different values based on the condition. If it's true the first value is returned, otherwise the second one. + * + *

          + * Syntax:

          + * + *
          + * if(<field|value|expression>, <return_value_if_true> [,<return_value_if_false>])
          + * 
          + * + *
          + * + *

          + * Examples:

          + * + *
          + * SELECT if(rich, 'rich', 'poor') FROM ...
          + * 
          + * SELECT if( eval( 'salary > 1000000' ), 'rich', 'poor') FROM ... + *
          + * + *
          + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ + +public class OSQLFunctionIf extends OSQLFunctionAbstract { + + public static final String NAME = "if"; + + public OSQLFunctionIf() { + super(NAME, 2, 3); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, final Object[] iParams, + final OCommandContext iContext) { + + boolean result; + + try { + Object condition = iParams[0]; + if (condition instanceof Boolean) + result = (Boolean) condition; + else if (condition instanceof String) + result = Boolean.parseBoolean(condition.toString()); + else if (condition instanceof Number) + result = ((Number) condition).intValue() > 0; + else + return null; + + return result ? iParams[1] : iParams[2]; + + } catch (Exception e) { + return null; + } + } + + @Override + public String getSyntax() { + return "if(, [,])"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIfNull.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIfNull.java new file mode 100644 index 00000000000..9eada8b9afa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionIfNull.java @@ -0,0 +1,87 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Returns the passed field/value (or optional parameter return_value_if_not_null) if + * field/value is not null; otherwise it returns return_value_if_null. + * + *

          + * Syntax:

          + * + *
          + * ifnull(<field|value>, <return_value_if_null> [,<return_value_if_not_null>])
          + * 
          + * + *
          + * + *

          + * Examples:

          + * + *
          + * SELECT ifnull('a', 'b') FROM ...
          + *  -> 'a'
          + * 
          + * SELECT ifnull('a', 'b', 'c') FROM ...
          + *  -> 'c'
          + * 
          + * SELECT ifnull(null, 'b') FROM ...
          + *  -> 'b'
          + * 
          + * SELECT ifnull(null, 'b', 'c') FROM ...
          + *  -> 'b'
          + * 
          + * + *
          + * + * @author Mark Bigler + */ + +public class OSQLFunctionIfNull extends OSQLFunctionAbstract { + + public static final String NAME = "ifnull"; + + public OSQLFunctionIfNull() { + super(NAME, 2, 3); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, final Object[] iParams, final OCommandContext iContext) { + /* + * iFuncParams [0] field/value to check for null [1] return value if [0] is null [2] optional return value if [0] is not null + */ + if (iParams[0] != null) { + if (iParams.length == 3) { + return iParams[2]; + } + return iParams[0]; + } + return iParams[1]; + } + + @Override + public String getSyntax() { + return "Syntax error: ifnull(, [,])"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionSysdate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionSysdate.java new file mode 100644 index 00000000000..89b291044b8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionSysdate.java @@ -0,0 +1,80 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Returns the current date time. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * @see OSQLFunctionDate + * + */ +public class OSQLFunctionSysdate extends OSQLFunctionAbstract { + public static final String NAME = "sysdate"; + + private final Date now; + private SimpleDateFormat format; + + /** + * Get the date at construction to have the same date for all the iteration. + */ + public OSQLFunctionSysdate() { + super(NAME, 0, 2); + now = new Date(); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + if (iParams.length == 0) + return now; + + if (format == null) { + format = new SimpleDateFormat((String) iParams[0]); + if (iParams.length == 2) + format.setTimeZone(TimeZone.getTimeZone(iParams[1].toString())); + else + format.setTimeZone(ODateHelper.getDatabaseTimeZone()); + } + + return format.format(now); + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return false; + } + + public String getSyntax() { + return "sysdate([] [,])"; + } + + @Override + public Object getResult() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionUUID.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionUUID.java new file mode 100644 index 00000000000..94525742946 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLFunctionUUID.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +import java.util.UUID; + +/** + * Generates a UUID as a 128-bits value using the Leach-Salz variant. For more information look at: + * http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLFunctionUUID extends OSQLFunctionAbstract { + public static final String NAME = "uuid"; + + /** + * Get the date at construction to have the same date for all the iteration. + */ + public OSQLFunctionUUID() { + super(NAME, 0, 0); + } + + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, final Object[] iParams, + OCommandContext iContext) { + return UUID.randomUUID().toString(); + } + + public boolean aggregateResults(final Object[] configuredParameters) { + return false; + } + + public String getSyntax() { + return "uuid()"; + } + + @Override + public Object getResult() { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodExclude.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodExclude.java new file mode 100644 index 00000000000..b30aa6fb91f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodExclude.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Filter the content by excluding only some fields. If the content is a document, then creates a copy without the excluded fields. + * If it's a collection of documents it acts against on each single entry. + *

          + *

          + * Syntax:

          + *

          + *

          + * exclude(<field|value|expression> [,<field-name>]* )
          + * 
          + *

          + *

          + *

          + *

          + * Examples:

          + *

          + *

          + * SELECT exclude(roles, 'permissions') FROM OUser
          + * 
          + *

          + *

          + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ + +public class OSQLMethodExclude extends OAbstractSQLMethod { + + public static final String NAME = "exclude"; + + public OSQLMethodExclude() { + super(NAME, 1, -1); + } + + @Override + public String getSyntax() { + return "Syntax error: exclude([][,]*)"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis != null) { + if (iThis instanceof ORecordId) { + iThis = ((ORecordId) iThis).getRecord(); + } + if (iThis instanceof ODocument) { + // ACT ON SINGLE DOCUMENT + return copy((ODocument) iThis, iParams); + } else if (iThis instanceof Map) { + // ACT ON SINGLE MAP + return copy((Map) iThis, iParams); + } else if (OMultiValue.isMultiValue(iThis)) { + // ACT ON MULTIPLE DOCUMENTS + final List result = new ArrayList(OMultiValue.getSize(iThis)); + for (Object o : OMultiValue.getMultiValueIterable(iThis, false)) { + if (o instanceof OIdentifiable) { + result.add(copy((ODocument) ((OIdentifiable) o).getRecord(), iParams)); + } + } + return result; + } + } + + // INVALID, RETURN NULL + return null; + } + + private Object copy(final ODocument document, final Object[] iFieldNames) { + final ODocument doc = document.copy(); + for (Object iFieldName : iFieldNames) { + if (iFieldName != null) { + final String fieldName = iFieldName.toString(); + if (fieldName.endsWith("*")) { + final String fieldPart = fieldName.substring(0, fieldName.length() - 1); + final List toExclude = new ArrayList(); + for (String f : doc.fieldNames()) { + if (f.startsWith(fieldPart)) + toExclude.add(f); + } + + for (String f : toExclude) + doc.removeField(f); + + } else + doc.removeField(fieldName); + } + } + doc.deserializeFields(); + return doc; + } + + private Object copy(final Map map, final Object[] iFieldNames) { + final ODocument doc = new ODocument().fields(map); + for (Object iFieldName : iFieldNames) { + if (iFieldName != null) { + final String fieldName = iFieldName.toString(); + + if (fieldName.endsWith("*")) { + final String fieldPart = fieldName.substring(0, fieldName.length() - 1); + final List toExclude = new ArrayList(); + for (String f : doc.fieldNames()) { + if (f.startsWith(fieldPart)) + toExclude.add(f); + } + + for (String f : toExclude) + doc.removeField(f); + + } else + doc.removeField(fieldName); + } + } + return doc; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodInclude.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodInclude.java new file mode 100644 index 00000000000..ffae2ccdcf0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/misc/OSQLMethodInclude.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Filter the content by including only some fields. If the content is a document, then creates a copy with only the included + * fields. If it's a collection of documents it acts against on each single entry. + *

          + *

          + * Syntax:

          + *

          + *

          + * include(<field|value|expression> [,<field-name>]* )
          + * 
          + *

          + *

          + *

          + *

          + * Examples:

          + *

          + *

          + * SELECT include(roles, 'name') FROM OUser
          + * 
          + *

          + *

          + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ + +public class OSQLMethodInclude extends OAbstractSQLMethod { + + public static final String NAME = "include"; + + public OSQLMethodInclude() { + super(NAME, 1, -1); + } + + @Override + public String getSyntax() { + return "Syntax error: include([][,]*)"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + + if (iParams[0] != null) { + if (iThis instanceof OIdentifiable) { + iThis = ((OIdentifiable) iThis).getRecord(); + } + if (iThis instanceof ODocument) { + // ACT ON SINGLE DOCUMENT + return copy((ODocument) iThis, iParams); + } else if (iThis instanceof Map) { + // ACT ON MAP + return copy((Map) iThis, iParams); + } else if (OMultiValue.isMultiValue(iThis)) { + // ACT ON MULTIPLE DOCUMENTS + final List result = new ArrayList(OMultiValue.getSize(iThis)); + for (Object o : OMultiValue.getMultiValueIterable(iThis, false)) { + if (o instanceof OIdentifiable) { + result.add(copy((ODocument) ((OIdentifiable) o).getRecord(), iParams)); + } + } + return result; + } + } + + // INVALID, RETURN NULL + return null; + } + + private Object copy(final ODocument document, final Object[] iFieldNames) { + final ODocument doc = new ODocument(); + for (int i = 0; i < iFieldNames.length; ++i) { + if (iFieldNames[i] != null) { + final String fieldName = iFieldNames[i].toString(); + + if (fieldName.endsWith("*")) { + final String fieldPart = fieldName.substring(0, fieldName.length() - 1); + final List toInclude = new ArrayList(); + for (String f : document.fieldNames()) { + if (f.startsWith(fieldPart)) + toInclude.add(f); + } + + for (String f : toInclude) + doc.field(fieldName, document.field(f)); + + } else + doc.field(fieldName, document.field(fieldName)); + } + } + return doc; + } + + private Object copy(final Map map, final Object[] iFieldNames) { + final ODocument doc = new ODocument(); + for (int i = 0; i < iFieldNames.length; ++i) { + if (iFieldNames[i] != null) { + final String fieldName = iFieldNames[i].toString(); + + if (fieldName.endsWith("*")) { + final String fieldPart = fieldName.substring(0, fieldName.length() - 1); + final List toInclude = new ArrayList(); + for (Object f : map.keySet()) { + if (f.toString().startsWith(fieldPart)) + toInclude.add(f.toString()); + } + + for (String f : toInclude) + doc.field(fieldName, map.get(f)); + + } else + doc.field(fieldName, map.get(fieldName)); + } + } + return doc; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/sequence/OSQLFunctionSequence.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/sequence/OSQLFunctionSequence.java new file mode 100644 index 00000000000..54dca621d16 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/sequence/OSQLFunctionSequence.java @@ -0,0 +1,54 @@ +package com.orientechnologies.orient.core.sql.functions.sequence; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +/** + * Returns a sequence by name. + * + * @author Luca Garulli + */ +public class OSQLFunctionSequence extends OSQLFunctionConfigurableAbstract { + public static final String NAME = "sequence"; + + public OSQLFunctionSequence() { + super(NAME, 1, 1); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, + OCommandContext iContext) { + final String seqName; + if (configuredParameters[0] instanceof OSQLFilterItem) + seqName = (String) ((OSQLFilterItem) configuredParameters[0]).getValue(iCurrentRecord, iCurrentResult, iContext); + else + seqName = configuredParameters[0].toString(); + + OSequence result = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSequenceLibrary().getSequence(seqName); + if (result == null) { + throw new OCommandExecutionException("Sequence not found: " + seqName); + } + return result; + } + + @Override + public Object getResult() { + return null; + } + + @Override + public String getSyntax() { + return "sequence()"; + } + + @Override + public boolean aggregateResults() { + return false; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMedian.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMedian.java new file mode 100644 index 00000000000..d0b9d41b5e0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMedian.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.stat; + +/** + * Computes the median for a field. Nulls are ignored in the calculation. + * + * Extends and forces the {@link OSQLFunctionPercentile} with the 50th percentile. + * + * @author Fabrizio Fortino + */ +public class OSQLFunctionMedian extends OSQLFunctionPercentile { + + public static final String NAME = "median"; + + public OSQLFunctionMedian() { + super(NAME, 1, 1); + this.quantiles.add(.5); + } + + @Override + public String getSyntax() { + return NAME + "()"; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMode.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMode.java new file mode 100644 index 00000000000..154ccbefcdc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionMode.java @@ -0,0 +1,124 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.stat; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Compute the mode (or multimodal) value for a field. The scores in the field's distribution that occurs more frequently. Nulls are + * ignored in the calculation. + * + * @author Fabrizio Fortino + */ +public class OSQLFunctionMode extends OSQLFunctionAbstract { + + public static final String NAME = "mode"; + + private Map seen = new HashMap(); + private int max = 0; + private List maxElems = new ArrayList(); + + public OSQLFunctionMode() { + super(NAME, 1, 1); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, + OCommandContext iContext) { + + if (OMultiValue.isMultiValue(iParams[0])) { + for (Object o : OMultiValue.getMultiValueIterable(iParams[0])) { + max = evaluate(o, 1, seen, maxElems, max); + } + } else { + max = evaluate(iParams[0], 1, seen, maxElems, max); + } + return getResult(); + } + + @Override + public Object getResult() { + if (returnDistributedResult()) { + return seen; + } else { + return maxElems.isEmpty() ? null : maxElems; + } + } + + @Override + public String getSyntax() { + return NAME + "()"; + } + + @Override + public boolean aggregateResults() { + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + Map dSeen = new HashMap(); + int dMax = 0; + List dMaxElems = new ArrayList(); + for (Object iParameter : resultsToMerge) { + final Map mSeen = (Map) iParameter; + for (Entry o : mSeen.entrySet()) { + dMax = this.evaluate(o.getKey(), o.getValue(), dSeen, dMaxElems, dMax); + } + } + return dMaxElems; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + private int evaluate(Object value, int times, Map iSeen, List iMaxElems, int iMax) { + if (value != null) { + if (iSeen.containsKey(value)) { + iSeen.put(value, iSeen.get(value) + times); + } else { + iSeen.put(value, times); + } + if (iSeen.get(value) > iMax) { + iMax = iSeen.get(value); + iMaxElems.clear(); + iMaxElems.add(value); + } else if (iSeen.get(value) == iMax) { + iMaxElems.add(value); + } + } + return iMax; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionPercentile.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionPercentile.java new file mode 100644 index 00000000000..5500c702650 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionPercentile.java @@ -0,0 +1,158 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.stat; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Computes the percentile for a field. Nulls are ignored in the calculation. + * + * @author Fabrizio Fortino + */ +public class OSQLFunctionPercentile extends OSQLFunctionAbstract { + + public static final String NAME = "percentile"; + + protected List quantiles = new ArrayList(); + private List values = new ArrayList(); + + public OSQLFunctionPercentile() { + this(NAME, 2, -1); + } + + public OSQLFunctionPercentile(final String iName, final int iMinParams, final int iMaxParams) { + super(iName, iMaxParams, iMaxParams); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, + OCommandContext iContext) { + + if (quantiles.isEmpty()) { // set quantiles once + for (int i = 1; i < iParams.length; ++i) { + this.quantiles.add(Double.parseDouble(iParams[i].toString())); + } + } + + if (iParams[0] instanceof Number) { + addValue((Number) iParams[0]); + } else if (OMultiValue.isMultiValue(iParams[0])) { + for (Object n : OMultiValue.getMultiValueIterable(iParams[0])) { + addValue((Number) n); + } + } + return null; + } + + @Override + public boolean aggregateResults() { + return true; + } + + @Override + public Object getResult() { + if (returnDistributedResult()) { + return values; + } else { + return this.evaluate(this.values); + } + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + List dValues = new ArrayList(); + for (Object iParameter : resultsToMerge) { + dValues.addAll((List) iParameter); + } + return this.evaluate(dValues); + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + @Override + public String getSyntax() { + return NAME + "(, [,*])"; + } + + private void addValue(Number value) { + if (value != null) { + this.values.add(value); + } + } + + private Object evaluate(List iValues) { + if (iValues.isEmpty()) { // result set is empty + return null; + } + if (quantiles.size() > 1) { + List results = new ArrayList(); + for (Double q : this.quantiles) { + results.add(this.evaluate(iValues, q)); + } + return results; + } else { + return this.evaluate(iValues, this.quantiles.get(0)); + } + } + + private Number evaluate(List iValues, double iQuantile) { + Collections.sort(iValues, new Comparator() { + @Override + public int compare(Number o1, Number o2) { + Double d1 = o1.doubleValue(); + Double d2 = o2.doubleValue(); + return d1.compareTo(d2); + } + }); + + double n = iValues.size(); + double pos = iQuantile * (n + 1); + + if (pos < 1) { + return iValues.get(0); + } + if (pos >= n) { + return iValues.get((int) n - 1); + } + + double fpos = Math.floor(pos); + int intPos = (int) fpos; + double dif = pos - fpos; + + double lower = iValues.get(intPos - 1).doubleValue(); + double upper = iValues.get(intPos).doubleValue(); + return lower + dif * (upper - lower); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionStandardDeviation.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionStandardDeviation.java new file mode 100644 index 00000000000..bfbdf2edb82 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionStandardDeviation.java @@ -0,0 +1,60 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.stat; + +import java.util.List; + +/** + * Compute the standard deviation for a given field. + * + * @author Fabrizio Fortino + */ +public class OSQLFunctionStandardDeviation extends OSQLFunctionVariance { + + public static final String NAME = "stddev"; + + public OSQLFunctionStandardDeviation() { + super(NAME, 1, 1); + } + + @Override + public Object getResult() { + return this.evaluate(super.getResult()); + } + + @Override + public Object mergeDistributedResult(List resultsToMerge) { + return this.evaluate(super.mergeDistributedResult(resultsToMerge)); + } + + @Override + public String getSyntax() { + return NAME + "()"; + } + + private Double evaluate(Object variance) { + Double result = null; + if (variance != null) { + result = Math.sqrt((Double) variance); + } + + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionVariance.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionVariance.java new file mode 100644 index 00000000000..468fa5d717a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/stat/OSQLFunctionVariance.java @@ -0,0 +1,158 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.stat; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Compute the variance estimation for a given field. + *

          + * This class uses the Weldford's algorithm (presented in Donald Knuth's Art of Computer Programming) to avoid multiple distribution + * values' passes. When executed in distributed mode it uses the Chan at al. pairwise variance algorithm to merge the results. + *

          + *

          + * References + *

          + *

          + *

            + *

            + *

          • Cook, John D. Accurately computing running variance.
          • + *

            + *

          • Knuth, Donald E. (1998) The Art of Computer Programming, Volume 2: Seminumerical Algorithms, 3rd Edition.
          • + *

            + *

          • Welford, B. P. (1962) Note on a method for calculating corrected sums of squares and products. Technometrics
          • + *

            + *

          • Chan, Tony F.; Golub, Gene H.; LeVeque, Randall J. (1979), Parallel Algorithm.
          • + *

            + *

          + * + * @author Fabrizio Fortino + */ +public class OSQLFunctionVariance extends OSQLFunctionAbstract { + + public static final String NAME = "variance"; + + private long n; + private double mean; + private double m2; + + public OSQLFunctionVariance() { + super(NAME, 1, 1); + } + + public OSQLFunctionVariance(final String iName, final int iMinParams, final int iMaxParams) { + super(iName, iMaxParams, iMaxParams); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams, + OCommandContext iContext) { + if (iParams[0] instanceof Number) { + addValue((Number) iParams[0]); + } else if (OMultiValue.isMultiValue(iParams[0])) { + for (Object n : OMultiValue.getMultiValueIterable(iParams[0])) { + addValue((Number) n); + } + } + return null; + } + + @Override + public boolean aggregateResults() { + return true; + } + + @Override + public Object getResult() { + if (returnDistributedResult()) { + final Map doc = new HashMap(); + doc.put("n", n); + doc.put("mean", mean); + doc.put("var", this.evaluate()); + return doc; + } else { + return this.evaluate(); + } + } + + @SuppressWarnings("unchecked") + @Override + public Object mergeDistributedResult(List resultsToMerge) { + if (returnDistributedResult()) { + long dN = 0; + double dMean = 0; + Double var = null; + for (Object iParameter : resultsToMerge) { + final Map item = (Map) iParameter; + if (dN == 0) { // first element + dN = (Long) item.get("n"); + dMean = (Double) item.get("mean"); + var = (Double) item.get("var"); + } else { + long rhsN = (Long) item.get("n"); + double rhsMean = (Double) item.get("mean"); + double rhsVar = (Double) item.get("var"); + + long totalN = dN + rhsN; + double totalMean = ((dMean * dN) + (rhsMean * rhsN)) / totalN; + + var = (((dN * var) + (rhsN * rhsVar)) / totalN) + ((dN * rhsN) * Math.pow((rhsMean - dMean) / totalN, 2)); + dN = totalN; + dMean = totalMean; + } + + } + return var; + } + + if (!resultsToMerge.isEmpty()) + return resultsToMerge.get(0); + + return null; + } + + @Override + public String getSyntax() { + return NAME + "()"; + } + + private void addValue(Number value) { + if (value != null) { + ++n; + double doubleValue = value.doubleValue(); + double nextM = mean + (doubleValue - mean) / n; + m2 += (doubleValue - mean) * (doubleValue - nextM); + mean = nextM; + } + } + + private Double evaluate() { + return n > 1 ? m2 / n : null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionConcat.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionConcat.java new file mode 100644 index 00000000000..7af3d21661b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionConcat.java @@ -0,0 +1,45 @@ +package com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionConfigurableAbstract; + +public class OSQLFunctionConcat extends OSQLFunctionConfigurableAbstract{ + public static final String NAME = "concat"; + private StringBuilder sb; + + public OSQLFunctionConcat() { + super(NAME, 1, 2); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, + Object iCurrentResult, Object[] iParams, OCommandContext iContext) { + if(sb==null) + { + sb = new StringBuilder(); + } + else + { + if(iParams.length>1) sb.append(iParams[1]); + } + sb.append(iParams[0]); + return null; + } + + @Override + public Object getResult() { + return sb!=null?sb.toString():null; + } + + @Override + public String getSyntax() { + return "concat(, [])"; + } + + @Override + public boolean aggregateResults() { + return true; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionFormat.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionFormat.java new file mode 100755 index 00000000000..a9743316ef0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLFunctionFormat.java @@ -0,0 +1,52 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract; + +/** + * Formats content. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLFunctionFormat extends OSQLFunctionAbstract { + public static final String NAME = "format"; + + public OSQLFunctionFormat() { + super(NAME, 1, -1); + } + + public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] params, + OCommandContext iContext) { + final Object[] args = new Object[params.length - 1]; + + for (int i = 0; i < args.length; ++i) + args[i] = params[i + 1]; + + return String.format((String) params[0], args); + } + + public String getSyntax() { + return "format(, [,]*)"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodAppend.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodAppend.java new file mode 100644 index 00000000000..96df7e7901c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodAppend.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Appends strings. Acts as a concatenation. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAppend extends OAbstractSQLMethod { + + public static final String NAME = "append"; + + public OSQLMethodAppend() { + super(NAME, 1, -1); + } + + @Override + public String getSyntax() { + return "append([]*)"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) + return iThis; + + final StringBuilder buffer = new StringBuilder(iThis.toString()); + for (int i = 0; i < iParams.length; ++i) { + if (iParams[i] != null) { + buffer.append(OIOUtils.getStringContent(iParams[i])); + } + } + + return buffer.toString(); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodFromJSON.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodFromJSON.java new file mode 100644 index 00000000000..41982915156 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodFromJSON.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Converts a document in JSON string. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodFromJSON extends OAbstractSQLMethod { + + public static final String NAME = "fromjson"; + + public OSQLMethodFromJSON() { + super(NAME, 0, 1); + } + + @Override + public String getSyntax() { + return "fromJSON([])"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis instanceof String) { + if (iParams.length > 0) { + final ODocument doc = new ODocument().fromJSON(iThis.toString(), iParams[0].toString()); + if (iParams[0].toString().contains("embedded")) + ODocumentInternal.addOwner(doc, iCurrentRecord.getRecord()); + + return doc; + } + + return new ODocument().fromJSON(iThis.toString().toString()); + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodHash.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodHash.java new file mode 100755 index 00000000000..b9d1b1a0c01 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodHash.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.security.OSecurityManager; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Hash a string supporting multiple algorithm, all those supported by JVM + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + */ +public class OSQLMethodHash extends OAbstractSQLMethod { + + public static final String NAME = "hash"; + + public OSQLMethodHash() { + super(NAME, 0, 1); + } + + @Override + public String getSyntax() { + return "hash([])"; + } + + @Override + public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, + final Object ioResult, final Object[] iParams) { + if (iThis == null) + return null; + + final String algorithm = iParams.length > 0 ? iParams[0].toString() : OSecurityManager.HASH_ALGORITHM; + try { + return OSecurityManager.createHash(iThis.toString(), algorithm); + + } catch (NoSuchAlgorithmException e) { + throw OException.wrapException(new OCommandExecutionException("hash(): algorithm '" + algorithm + "' is not supported"), e); + } catch (UnsupportedEncodingException e) { + throw OException.wrapException(new OCommandExecutionException("hash(): encoding 'UTF-8' is not supported"), e); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodLength.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodLength.java new file mode 100644 index 00000000000..9a5ef093364 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodLength.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns the string length. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodLength extends OAbstractSQLMethod { + + public static final String NAME = "length"; + + public OSQLMethodLength() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "length()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null) { + return 0; + } + + return iThis.toString().length(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodReplace.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodReplace.java new file mode 100644 index 00000000000..489491c1a2c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodReplace.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Replaces all the occurrences. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodReplace extends OAbstractSQLMethod { + + public static final String NAME = "replace"; + + public OSQLMethodReplace() { + super(NAME, 2, 2); + } + + @Override + public String getSyntax() { + return "replace(, )"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null || iParams[1] == null) { + return iParams[0]; + } + + return iThis.toString().replace(iParams[0].toString(), iParams[1].toString()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodRight.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodRight.java new file mode 100644 index 00000000000..279efc31226 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodRight.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns the first characters from the end of the string. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodRight extends OAbstractSQLMethod { + + public static final String NAME = "right"; + + public OSQLMethodRight() { + super(NAME, 1, 1); + } + + @Override + public String getSyntax() { + return "right( )"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) { + return null; + } + + final String valueAsString = iThis.toString(); + + final int offset = Integer.parseInt(iParams[0].toString()); + return valueAsString.substring(offset < valueAsString.length() ? valueAsString.length() - offset : 0); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodSubString.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodSubString.java new file mode 100644 index 00000000000..c2cc1c78c3f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodSubString.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Extracts a sub string from the original. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodSubString extends OAbstractSQLMethod { + + public static final String NAME = "substring"; + + public OSQLMethodSubString() { + super(NAME, 1, 2); + } + + @Override public String getSyntax() { + return "subString( [,])"; + } + + @Override public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, + Object[] iParams) { + if (iThis == null || iParams[0] == null) { + return null; + } + + if (iParams.length > 1) { + int from = Integer.parseInt(iParams[0].toString()); + int to = Integer.parseInt(iParams[1].toString()); + String thisString = iThis.toString(); + if (from < 0) { + from = 0; + } + if (from >= thisString.length()) { + return ""; + } + if (to > thisString.length()) { + to = thisString.length(); + } + if (to <= from) { + return ""; + } + + return thisString.substring(from, to); + } else { + int from = Integer.parseInt(iParams[0].toString()); + String thisString = iThis.toString(); + if (from < 0) { + from = 0; + } + if (from >= thisString.length()) { + return ""; + } + return thisString.substring(from); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodToJSON.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodToJSON.java new file mode 100644 index 00000000000..f454a8b24a3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/OSQLMethodToJSON.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.functions.text; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +import java.util.Map; + +/** + * Converts a document in JSON string. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodToJSON extends OAbstractSQLMethod { + + public static final String NAME = "tojson"; + + public OSQLMethodToJSON() { + super(NAME, 0, 1); + } + + @Override + public String getSyntax() { + return "toJSON([])"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null) { + return null; + } + + final String format = iParams.length > 0 ? ((String) iParams[0]).replace("\"", "") : null; + + if (iThis instanceof ORecord) { + + final ORecord record = (ORecord) iThis; + return iParams.length == 1 ? record.toJSON(format) : record.toJSON(); + + } else if (iThis instanceof Map) { + + final ODocument doc = new ODocument().fromMap((Map) iThis); + return iParams.length == 1 ? doc.toJSON(format) : doc.toJSON(); + + } else if (OMultiValue.isMultiValue(iThis)) { + + StringBuilder builder = new StringBuilder(); + builder.append("["); + for (Object o : OMultiValue.getMultiValueIterable(iThis, false)) { + builder.append(execute(o, iCurrentRecord, iContext, ioResult, iParams)); + } + + builder.append("]"); + return builder.toString(); + } + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/package-info.java b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/package-info.java new file mode 100644 index 00000000000..ce11504b253 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/functions/text/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2013 Orient Technologies LTD (info--at--orientechnologies.com) + * + * Licensed 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. + */ +/** + * @author Luca Garulli + * + */ +package com.orientechnologies.orient.core.sql.functions.text; \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/ODefaultSQLMethodFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/ODefaultSQLMethodFactory.java new file mode 100755 index 00000000000..2b7b69d1db3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/ODefaultSQLMethodFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.sql.functions.coll.OSQLMethodMultiValue; +import com.orientechnologies.orient.core.sql.functions.conversion.OSQLMethodAsDate; +import com.orientechnologies.orient.core.sql.functions.conversion.OSQLMethodAsDateTime; +import com.orientechnologies.orient.core.sql.functions.conversion.OSQLMethodAsDecimal; +import com.orientechnologies.orient.core.sql.functions.conversion.OSQLMethodConvert; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLMethodExclude; +import com.orientechnologies.orient.core.sql.functions.misc.OSQLMethodInclude; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodAppend; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodFromJSON; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodHash; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodLength; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodReplace; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodRight; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodSubString; +import com.orientechnologies.orient.core.sql.functions.text.OSQLMethodToJSON; +import com.orientechnologies.orient.core.sql.method.misc.*; +import com.orientechnologies.orient.core.sql.method.sequence.OSQLMethodCurrent; +import com.orientechnologies.orient.core.sql.method.sequence.OSQLMethodNext; +import com.orientechnologies.orient.core.sql.method.sequence.OSQLMethodReset; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Default method factory. + * + * @author Johann Sorel (Geomatys) + */ +public class ODefaultSQLMethodFactory implements OSQLMethodFactory { + + private final Map methods = new HashMap(); + + public ODefaultSQLMethodFactory() { + register(OSQLMethodAppend.NAME, new OSQLMethodAppend()); + register(OSQLMethodAsBoolean.NAME, new OSQLMethodAsBoolean()); + register(OSQLMethodAsDate.NAME, new OSQLMethodAsDate()); + register(OSQLMethodAsDateTime.NAME, new OSQLMethodAsDateTime()); + register(OSQLMethodAsDecimal.NAME, new OSQLMethodAsDecimal()); + register(OSQLMethodAsFloat.NAME, new OSQLMethodAsFloat()); + register(OSQLMethodAsInteger.NAME, new OSQLMethodAsInteger()); + register(OSQLMethodAsList.NAME, new OSQLMethodAsList()); + register(OSQLMethodAsLong.NAME, new OSQLMethodAsLong()); + register(OSQLMethodAsMap.NAME, new OSQLMethodAsMap()); + register(OSQLMethodAsSet.NAME, new OSQLMethodAsSet()); + register(OSQLMethodAsString.NAME, new OSQLMethodAsString()); + register(OSQLMethodCharAt.NAME, new OSQLMethodCharAt()); + register(OSQLMethodConvert.NAME, new OSQLMethodConvert()); + register(OSQLMethodExclude.NAME, new OSQLMethodExclude()); + register(OSQLMethodField.NAME, new OSQLMethodField()); + register(OSQLMethodFormat.NAME, new OSQLMethodFormat()); + register(OSQLMethodFromJSON.NAME, new OSQLMethodFromJSON()); + register(OSQLMethodFunctionDelegate.NAME, OSQLMethodFunctionDelegate.class); + register(OSQLMethodHash.NAME, new OSQLMethodHash()); + register(OSQLMethodInclude.NAME, new OSQLMethodInclude()); + register(OSQLMethodIndexOf.NAME, new OSQLMethodIndexOf()); + register(OSQLMethodJavaType.NAME, new OSQLMethodJavaType()); + register(OSQLMethodKeys.NAME, new OSQLMethodKeys()); + register(OSQLMethodLastIndexOf.NAME, new OSQLMethodLastIndexOf()); + register(OSQLMethodLeft.NAME, new OSQLMethodLeft()); + register(OSQLMethodLength.NAME, new OSQLMethodLength()); + register(OSQLMethodMultiValue.NAME, new OSQLMethodMultiValue()); + register(OSQLMethodNormalize.NAME, new OSQLMethodNormalize()); + register(OSQLMethodPrefix.NAME, new OSQLMethodPrefix()); + register(OSQLMethodRemove.NAME, new OSQLMethodRemove()); + register(OSQLMethodRemoveAll.NAME, new OSQLMethodRemoveAll()); + register(OSQLMethodReplace.NAME, new OSQLMethodReplace()); + register(OSQLMethodRight.NAME, new OSQLMethodRight()); + register(OSQLMethodSize.NAME, new OSQLMethodSize()); + register(OSQLMethodSplit.NAME, new OSQLMethodSplit()); + register(OSQLMethodToLowerCase.NAME, new OSQLMethodToLowerCase()); + register(OSQLMethodToUpperCase.NAME, new OSQLMethodToUpperCase()); + register(OSQLMethodTrim.NAME, new OSQLMethodTrim()); + register(OSQLMethodType.NAME, new OSQLMethodType()); + register(OSQLMethodSubString.NAME, new OSQLMethodSubString()); + register(OSQLMethodToJSON.NAME, new OSQLMethodToJSON()); + register(OSQLMethodValues.NAME, new OSQLMethodValues()); + + // SEQUENCE + register(OSQLMethodCurrent.NAME, new OSQLMethodCurrent()); + register(OSQLMethodNext.NAME, new OSQLMethodNext()); + register(OSQLMethodReset.NAME, new OSQLMethodReset()); + } + + public void register(final String iName, final Object iImplementation) { + methods.put(iName.toLowerCase(Locale.ENGLISH), iImplementation); + } + + @Override + public boolean hasMethod(final String iName) { + return methods.containsKey(iName.toLowerCase(Locale.ENGLISH)); + } + + @Override + public Set getMethodNames() { + return methods.keySet(); + } + + @Override + public OSQLMethod createMethod(final String name) throws OCommandExecutionException { + final Object m = methods.get(name); + final OSQLMethod method; + + if (m instanceof Class) + try { + method = (OSQLMethod) ((Class) m).newInstance(); + } catch (Exception e) { + throw OException.wrapException(new OCommandExecutionException("Cannot create SQL method: " + m), e); + } + else + method = (OSQLMethod) m; + + if (method == null) + throw new OCommandExecutionException("Unknown method name: " + name); + + return method; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethod.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethod.java new file mode 100644 index 00000000000..f2d5d9c165f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethod.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Methods can be used on various objects with different number of arguments. SQL syntax : .([parameters]) + * + * @author Johann Sorel (Geomatys) + */ +public interface OSQLMethod extends Comparable { + + /** + * @return method name + */ + String getName(); + + /** + * Returns a convinient SQL String representation of the method. + *

          + * Example : + * + *

          +   *  field.myMethod( param1, param2, [optionalParam3])
          +   * 
          + * + * This text will be used in exception messages. + * + * @return String , never null. + */ + public String getSyntax(); + + /** + * @return minimum number of arguments requiered by this method + */ + int getMinParams(); + + /** + * @return maximum number of arguments requiered by this method + */ + int getMaxParams(); + + /** + * Process a record. + * + * + * + * @param iThis + * @param iCurrentRecord + * : current record + * @param iContext + * execution context + * @param ioResult + * : field value + * @param iParams + * : function parameters, number is ensured to be within minParams and maxParams. + * @return evaluation result + */ + Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams); + + boolean evaluateParameters(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodCharAt.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodCharAt.java new file mode 100644 index 00000000000..3884cbe76cd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodCharAt.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns a character in a string. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodCharAt extends OAbstractSQLMethod { + + public static final String NAME = "charat"; + + public OSQLMethodCharAt() { + super(NAME, 1, 1); + } + + @Override + public String getSyntax() { + return "charAt()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, + Object[] iParams) { + if (iParams[0] == null) { + return null; + } + + int index = Integer.parseInt(iParams[0].toString()); + return "" + iThis.toString().charAt(index); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodFactory.java new file mode 100644 index 00000000000..d45c2811ebb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method; + +import java.util.Set; + +import com.orientechnologies.orient.core.exception.OCommandExecutionException; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public interface OSQLMethodFactory { + + boolean hasMethod(String iName); + + /** + * @return Set of supported method names of this factory + */ + Set getMethodNames(); + + /** + * Create method for the given name. returned method may be a new instance each time or a constant. + * + * @param name + * @return OSQLMethod : created method + * @throws OCommandExecutionException + * : when method creation fail + */ + OSQLMethod createMethod(String name) throws OCommandExecutionException; + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodLeft.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodLeft.java new file mode 100644 index 00000000000..0651635c834 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodLeft.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns the first characters from the beginning of the string. + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodLeft extends OAbstractSQLMethod { + + public static final String NAME = "left"; + + public OSQLMethodLeft() { + super(NAME, 1, 1); + } + + @Override + public String getSyntax() { + return "left()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iParams[0] == null || iThis == null) { + return null; + } + + final String valueAsString = iThis.toString(); + + final int len = Integer.parseInt(iParams[0].toString()); + return valueAsString.substring(0, len <= valueAsString.length() ? len : valueAsString.length()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodRuntime.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodRuntime.java new file mode 100644 index 00000000000..99e877dc996 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/OSQLMethodRuntime.java @@ -0,0 +1,222 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.method; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.parser.OBaseParser; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.command.OCommandExecutorNotFoundException; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; +import com.orientechnologies.orient.core.sql.OCommandSQL; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemAbstract; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable; +import com.orientechnologies.orient.core.sql.filter.OSQLPredicate; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; + +import java.util.List; + +/** + * Wraps function managing the binding of parameters. + * + * @author Luca Garulli (l.garulli--at--orientechnologies.com) + * + */ +public class OSQLMethodRuntime extends OSQLFilterItemAbstract implements Comparable { + + public OSQLMethod method; + public Object[] configuredParameters; + public Object[] runtimeParameters; + + public OSQLMethodRuntime(final OBaseParser iQueryToParse, final String iText) { + super(iQueryToParse, iText); + } + + public OSQLMethodRuntime(final OSQLMethod iFunction) { + method = iFunction; + } + + /** + * Execute a method. + * + * @param iCurrentRecord + * Current record + * @param iCurrentResult + * TODO + * @param iContext + * @return + */ + public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, + final OCommandContext iContext) { + if (iThis == null) + return null; + + if (configuredParameters != null) { + // RESOLVE VALUES USING THE CURRENT RECORD + for (int i = 0; i < configuredParameters.length; ++i) { + runtimeParameters[i] = configuredParameters[i]; + + if (method.evaluateParameters()) { + if (configuredParameters[i] instanceof OSQLFilterItemField) { + runtimeParameters[i] = ((OSQLFilterItemField) configuredParameters[i]).getValue(iCurrentRecord, iCurrentResult, + iContext); + if (runtimeParameters[i] == null && iCurrentResult instanceof OIdentifiable) + // LOOK INTO THE CURRENT RESULT + runtimeParameters[i] = ((OSQLFilterItemField) configuredParameters[i]).getValue((OIdentifiable) iCurrentResult, + iCurrentResult, iContext); + } else if (configuredParameters[i] instanceof OSQLMethodRuntime) + runtimeParameters[i] = ((OSQLMethodRuntime) configuredParameters[i]).execute(iThis, iCurrentRecord, iCurrentResult, + iContext); + else if (configuredParameters[i] instanceof OSQLFunctionRuntime) + runtimeParameters[i] = ((OSQLFunctionRuntime) configuredParameters[i]).execute(iCurrentRecord, iCurrentRecord, iCurrentResult, + iContext); + else if (configuredParameters[i] instanceof OSQLFilterItemVariable) { + runtimeParameters[i] = ((OSQLFilterItemVariable) configuredParameters[i]).getValue(iCurrentRecord, iCurrentResult, + iContext); + if (runtimeParameters[i] == null && iCurrentResult instanceof OIdentifiable) + // LOOK INTO THE CURRENT RESULT + runtimeParameters[i] = ((OSQLFilterItemVariable) configuredParameters[i]).getValue((OIdentifiable) iCurrentResult, + iCurrentResult, iContext); + } else if (configuredParameters[i] instanceof OCommandSQL) { + try { + runtimeParameters[i] = ((OCommandSQL) configuredParameters[i]).setContext(iContext).execute(); + } catch (OCommandExecutorNotFoundException e) { + // TRY WITH SIMPLE CONDITION + final String text = ((OCommandSQL) configuredParameters[i]).getText(); + final OSQLPredicate pred = new OSQLPredicate(text); + runtimeParameters[i] = pred.evaluate(iCurrentRecord instanceof ORecord ? (ORecord) iCurrentRecord : null, + (ODocument) iCurrentResult, iContext); + // REPLACE ORIGINAL PARAM + configuredParameters[i] = pred; + + } + } else if (configuredParameters[i] instanceof OSQLPredicate) + runtimeParameters[i] = ((OSQLPredicate) configuredParameters[i]).evaluate(iCurrentRecord.getRecord(), + (iCurrentRecord instanceof ODocument ? (ODocument) iCurrentResult : null), iContext); + else if (configuredParameters[i] instanceof String) { + if (configuredParameters[i].toString().startsWith("\"") || configuredParameters[i].toString().startsWith("'")) + runtimeParameters[i] = OIOUtils.getStringContent(configuredParameters[i]); + } + } + } + + if (method.getMaxParams() == -1 || method.getMaxParams() > 0) { + if (runtimeParameters.length < method.getMinParams() + || (method.getMaxParams() > -1 && runtimeParameters.length > method.getMaxParams())) + throw new OCommandExecutionException("Syntax error: function '" + + method.getName() + + "' needs " + + (method.getMinParams() == method.getMaxParams() ? method.getMinParams() : method.getMinParams() + "-" + + method.getMaxParams()) + " argument(s) while has been received " + runtimeParameters.length); + } + } + + final Object functionResult = method.execute(iThis, iCurrentRecord, iContext, iCurrentResult, runtimeParameters); + + return transformValue(iCurrentRecord, iContext, functionResult); + } + + @Override + public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContext) { + final ODocument current = iRecord != null ? (ODocument) iRecord.getRecord() : null; + return execute(current, current, null, iContext); + } + + @Override + public String getRoot() { + return method.getName(); + } + + @Override + protected void setRoot(final OBaseParser iQueryToParse, final String iText) { + final int beginParenthesis = iText.indexOf('('); + + // SEARCH FOR THE FUNCTION + final String funcName = iText.substring(0, beginParenthesis); + + final List funcParamsText = OStringSerializerHelper.getParameters(iText); + + method = OSQLEngine.getInstance().getMethod(funcName); + if (method == null) + throw new OCommandSQLParsingException("Unknown method " + funcName + "()"); + + // PARSE PARAMETERS + this.configuredParameters = new Object[funcParamsText.size()]; + for (int i = 0; i < funcParamsText.size(); ++i) + this.configuredParameters[i] = funcParamsText.get(i); + + setParameters(configuredParameters, true); + } + + public OSQLMethodRuntime setParameters(final Object[] iParameters, final boolean iEvaluate) { + if (iParameters != null) { + this.configuredParameters = new Object[iParameters.length]; + for (int i = 0; i < iParameters.length; ++i) { + this.configuredParameters[i] = iParameters[i]; + + if (iParameters[i] != null) { + if (iParameters[i] instanceof String && !iParameters[i].toString().startsWith("[")) { + final Object v = OSQLHelper.parseValue(null, null, iParameters[i].toString(), null); + if (v == OSQLHelper.VALUE_NOT_PARSED + || (v != null && OMultiValue.isMultiValue(v) && OMultiValue.getFirstValue(v) == OSQLHelper.VALUE_NOT_PARSED)) + continue; + + configuredParameters[i] = v; + } + } else + this.configuredParameters[i] = null; + } + + // COPY STATIC VALUES + this.runtimeParameters = new Object[configuredParameters.length]; + for (int i = 0; i < configuredParameters.length; ++i) { + if (!(configuredParameters[i] instanceof OSQLFilterItemField) && !(configuredParameters[i] instanceof OSQLMethodRuntime)) + runtimeParameters[i] = configuredParameters[i]; + } + } + + return this; + } + + public OSQLMethod getMethod() { + return method; + } + + public Object[] getConfiguredParameters() { + return configuredParameters; + } + + public Object[] getRuntimeParameters() { + return runtimeParameters; + } + + @Override + public int compareTo(final OSQLMethodRuntime o) { + return method.compareTo(o.getMethod()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OAbstractSQLMethod.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OAbstractSQLMethod.java new file mode 100644 index 00000000000..113f746ab98 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OAbstractSQLMethod.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.method.OSQLMethod; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public abstract class OAbstractSQLMethod implements OSQLMethod { + + private final String name; + private final int minparams; + private final int maxparams; + + public OAbstractSQLMethod(String name) { + this(name, 0); + } + + public OAbstractSQLMethod(String name, int nbparams) { + this(name, nbparams, nbparams); + } + + public OAbstractSQLMethod(String name, int minparams, int maxparams) { + this.name = name; + this.minparams = minparams; + this.maxparams = maxparams; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSyntax() { + final StringBuilder sb = new StringBuilder("."); + sb.append(getName()); + sb.append('('); + for (int i = 0; i < minparams; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append("param"); + sb.append(i + 1); + } + if (minparams != maxparams) { + sb.append('['); + for (int i = minparams; i < maxparams; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append("param"); + sb.append(i + 1); + } + sb.append(']'); + } + sb.append(')'); + + return sb.toString(); + } + + @Override + public int getMinParams() { + return minparams; + } + + @Override + public int getMaxParams() { + return maxparams; + } + + protected Object getParameterValue(final OIdentifiable iRecord, final String iValue) { + if (iValue == null) { + return null; + } + + if (iValue.charAt(0) == '\'' || iValue.charAt(0) == '"') { + // GET THE VALUE AS STRING + return iValue.substring(1, iValue.length() - 1); + } + + if(iRecord == null){ + return null; + } + // SEARCH FOR FIELD + return ((ODocument) iRecord.getRecord()).field(iValue); + } + + @Override + public int compareTo(OSQLMethod o) { + return this.getName().compareTo(o.getName()); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean evaluateParameters() { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsBoolean.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsBoolean.java new file mode 100644 index 00000000000..0ab3b45b10c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsBoolean.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsBoolean extends OAbstractSQLMethod { + + public static final String NAME = "asboolean"; + + public OSQLMethodAsBoolean() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult != null) { + if (ioResult instanceof String) { + ioResult = Boolean.valueOf(((String) ioResult).trim()); + } else if (ioResult instanceof Number) { + return ((Number) ioResult).intValue() != 0; + } + } + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsFloat.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsFloat.java new file mode 100644 index 00000000000..c86167fd51e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsFloat.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsFloat extends OAbstractSQLMethod { + + public static final String NAME = "asfloat"; + + public OSQLMethodAsFloat() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof Number) { + ioResult = ((Number) ioResult).floatValue(); + } else { + ioResult = ioResult != null ? new Float(ioResult.toString().trim()) : null; + } + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsInteger.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsInteger.java new file mode 100644 index 00000000000..147a0750ae8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsInteger.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsInteger extends OAbstractSQLMethod { + + public static final String NAME = "asinteger"; + + public OSQLMethodAsInteger() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof Number) { + ioResult = ((Number) ioResult).intValue(); + } else { + ioResult = ioResult != null ? new Integer(ioResult.toString().trim()) : null; + } + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsList.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsList.java new file mode 100644 index 00000000000..8485f8ab37b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsList.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Transforms current value in a List. + * + * @author Luca Garulli + */ +public class OSQLMethodAsList extends OAbstractSQLMethod { + + public static final String NAME = "aslist"; + + public OSQLMethodAsList() { + super(NAME); + } + + @SuppressWarnings("unchecked") + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof List) + // ALREADY A LIST + { + return ioResult; + } + + if (ioResult == null) + // NULL VALUE, RETURN AN EMPTY LIST + { + return Collections.EMPTY_LIST; + } + + if (ioResult instanceof Collection) { + return new ArrayList((Collection) ioResult); + } else if (!(ioResult instanceof ODocument) && ioResult instanceof Iterable) { + ioResult = ((Iterable) ioResult).iterator(); + } + + if (ioResult instanceof Iterator) { + final List list = ioResult instanceof OSizeable ? new ArrayList(((OSizeable) ioResult).size()) + : new ArrayList(); + + for (Iterator iter = (Iterator) ioResult; iter.hasNext();) { + list.add(iter.next()); + } + return list; + } + + // SINGLE ITEM: ADD IT AS UNIQUE ITEM + return Collections.singletonList(ioResult); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsLong.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsLong.java new file mode 100644 index 00000000000..2f582c71d4f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsLong.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import java.util.Date; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsLong extends OAbstractSQLMethod { + + public static final String NAME = "aslong"; + + public OSQLMethodAsLong() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof Number) { + ioResult = ((Number) ioResult).longValue(); + } else if (ioResult instanceof Date) { + ioResult = ((Date) ioResult).getTime(); + } else { + ioResult = ioResult != null ? new Long(ioResult.toString().trim()) : null; + } + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsMap.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsMap.java new file mode 100644 index 00000000000..b3dec54c213 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsMap.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Transforms current value into a Map. + * + * @author Luca Garulli + */ +public class OSQLMethodAsMap extends OAbstractSQLMethod { + + public static final String NAME = "asmap"; + + public OSQLMethodAsMap() { + super(NAME); + } + + @SuppressWarnings("unchecked") + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof Map) + // ALREADY A MAP + { + return ioResult; + } + + if (ioResult == null) + // NULL VALUE, RETURN AN EMPTY MAP + { + return Collections.EMPTY_MAP; + } + + if (ioResult instanceof ODocument) + // CONVERT ODOCUMENT TO MAP + { + return ((ODocument) ioResult).toMap(); + } + + Iterator iter; + if (ioResult instanceof Iterator) { + iter = (Iterator) ioResult; + } else if (ioResult instanceof Iterable) { + iter = ((Iterable) ioResult).iterator(); + } else { + return null; + } + + final HashMap map = new HashMap(); + while (iter.hasNext()) { + final Object key = iter.next(); + if (iter.hasNext()) { + final Object value = iter.next(); + map.put(key, value); + } + } + + return map; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsSet.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsSet.java new file mode 100644 index 00000000000..8bf3800c06b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsSet.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * Transforms current value in a Set. + * + * @author Luca Garulli + */ +public class OSQLMethodAsSet extends OAbstractSQLMethod { + + public static final String NAME = "asset"; + + public OSQLMethodAsSet() { + super(NAME); + } + + @SuppressWarnings("unchecked") + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult instanceof Set) + // ALREADY A SET + { + return ioResult; + } + + if (ioResult == null) + // NULL VALUE, RETURN AN EMPTY SET + { + return Collections.EMPTY_SET; + } + + if (ioResult instanceof Collection) { + return new HashSet((Collection) ioResult); + } else if (!(ioResult instanceof ODocument) && ioResult instanceof Iterable) { + ioResult = ((Iterable) ioResult).iterator(); + } + + if (ioResult instanceof Iterator) { + final Set set = ioResult instanceof OSizeable ? new HashSet(((OSizeable) ioResult).size()) + : new HashSet(); + + for (Iterator iter = (Iterator) ioResult; iter.hasNext(); ) { + set.add(iter.next()); + } + return set; + } + + // SINGLE ITEM: ADD IT AS UNIQUE ITEM + return Collections.singleton(ioResult); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsString.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsString.java new file mode 100644 index 00000000000..3fe868f8634 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodAsString.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodAsString extends OAbstractSQLMethod { + + public static final String NAME = "asstring"; + + public OSQLMethodAsString() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null ? ioResult.toString() : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodField.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodField.java new file mode 100644 index 00000000000..e7730c19430 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodField.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.log.OLogManager; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodField extends OAbstractSQLMethod { + + public static final String NAME = "field"; + + public OSQLMethodField() { + super(NAME, 1, 1); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, Object ioResult, + final Object[] iParams) { + if (iParams[0] == null) + return null; + + final String paramAsString = iParams[0].toString(); + + if (ioResult != null) { + if (ioResult instanceof String) { + try { + ioResult = new ODocument(new ORecordId((String) ioResult)); + } catch (Exception e) { + OLogManager.instance().error(this, "Error on reading rid with value '%s'", null, ioResult); + ioResult = null; + } + } else if (ioResult instanceof OIdentifiable) { + ioResult = ((OIdentifiable) ioResult).getRecord(); + } else if (ioResult instanceof Collection || ioResult instanceof OMultiCollectionIterator + || ioResult.getClass().isArray()) { + final List result = new ArrayList(OMultiValue.getSize(ioResult)); + for (Object o : OMultiValue.getMultiValueIterable(ioResult, false)) { + Object newlyAdded = ODocumentHelper.getFieldValue(o, paramAsString); + if (OMultiValue.isMultiValue(newlyAdded)) { + if(newlyAdded instanceof Map || newlyAdded instanceof OIdentifiable){ + result.add(newlyAdded); + }else for (Object item : OMultiValue.getMultiValueIterable(newlyAdded)) { + result.add(item); + } + } else { + result.add(newlyAdded); + } + } + return result; + } + } + + if (!"*".equals(paramAsString) && ioResult != null) { + if (ioResult instanceof OCommandContext) { + ioResult = ((OCommandContext) ioResult).getVariable(paramAsString); + } else { + ioResult = ODocumentHelper.getFieldValue(ioResult, paramAsString, iContext); + } + } + + return ioResult; + } + + @Override + public boolean evaluateParameters() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFormat.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFormat.java new file mode 100644 index 00000000000..201d664428f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFormat.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.util.ODateHelper; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodFormat extends OAbstractSQLMethod { + + public static final String NAME = "format"; + + public OSQLMethodFormat() { + super(NAME, 1, 2); + } + + @Override + public Object execute(final Object iThis, final OIdentifiable iRecord, final OCommandContext iContext, Object ioResult, + final Object[] iParams) { + + // TRY TO RESOLVE AS DYNAMIC VALUE + Object v = getParameterValue(iRecord, iParams[0].toString()); + if (v == null) + // USE STATIC ONE + v = iParams[0].toString(); + + if (v != null) { + if (isCollectionOfDates(ioResult)) { + List result = new ArrayList(); + Iterator iterator = OMultiValue.getMultiValueIterator(ioResult); + final SimpleDateFormat format = new SimpleDateFormat(v.toString()); + if (iParams.length > 1) { + format.setTimeZone(TimeZone.getTimeZone(iParams[1].toString())); + } else { + format.setTimeZone(ODateHelper.getDatabaseTimeZone()); + } + while (iterator.hasNext()) { + result.add(format.format(iterator.next())); + } + ioResult = result; + } else if (ioResult instanceof Date) { + final SimpleDateFormat format = new SimpleDateFormat(v.toString()); + if (iParams.length > 1) { + format.setTimeZone(TimeZone.getTimeZone(iParams[1].toString())); + } else { + format.setTimeZone(ODateHelper.getDatabaseTimeZone()); + } + ioResult = format.format(ioResult); + } else { + ioResult = ioResult != null ? String.format(v.toString(), ioResult) : null; + } + } + + return ioResult; + } + + private boolean isCollectionOfDates(Object ioResult) { + if (OMultiValue.isMultiValue(ioResult)) { + Iterator iterator = OMultiValue.getMultiValueIterator(ioResult); + while (iterator.hasNext()) { + Object item = iterator.next(); + if (item != null && !(item instanceof Date)) { + return false; + } + } + return true; + } + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFunctionDelegate.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFunctionDelegate.java new file mode 100644 index 00000000000..f893c458eaa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodFunctionDelegate.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime; + +/** + * Delegates the execution to a function. + * + * @author Luca Garulli + */ +public class OSQLMethodFunctionDelegate extends OAbstractSQLMethod { + + public static final String NAME = "function"; + private OSQLFunctionRuntime func; + + public OSQLMethodFunctionDelegate(final OSQLFunction f) { + super(NAME); + func = new OSQLFunctionRuntime(f); + } + + @Override + public int getMinParams() { + final int min = func.getFunction().getMinParams(); + return min == -1 ? -1 : min - 1; + } + + @Override + public int getMaxParams() { + final int max = func.getFunction().getMaxParams(); + return max == -1 ? -1 : max - 1; + } + + @Override + public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, + final Object ioResult, final Object[] iParams) { + + func.setParameters(iParams, false); + + return func.execute(iThis, iCurrentRecord, ioResult, iContext); + } + + @Override + public String toString() { + return "function " + func; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodIndexOf.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodIndexOf.java new file mode 100644 index 00000000000..510b2aadcf8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodIndexOf.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodIndexOf extends OAbstractSQLMethod { + + public static final String NAME = "indexof"; + + public OSQLMethodIndexOf() { + super(NAME, 1, 2); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + final String toFind = OIOUtils.getStringContent(iParams[0].toString()); + int startIndex = iParams.length > 1 ? Integer.parseInt(iParams[1].toString()) : 0; + + return iThis != null ? iThis.toString().indexOf(toFind, startIndex) : null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodJavaType.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodJavaType.java new file mode 100755 index 00000000000..bf626baea07 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodJavaType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Returns the value's Java type. + * + * @author Luca Garulli + */ +public class OSQLMethodJavaType extends OAbstractSQLMethod { + + public static final String NAME = "javatype"; + + public OSQLMethodJavaType() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult == null) { + return null; + } + return ioResult.getClass().getName(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodKeys.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodKeys.java new file mode 100644 index 00000000000..b74614a969e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodKeys.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import java.util.Map; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodKeys extends OAbstractSQLMethod { + + public static final String NAME = "keys"; + + public OSQLMethodKeys() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null && ioResult instanceof Map ? ((Map) ioResult).keySet() : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodLastIndexOf.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodLastIndexOf.java new file mode 100644 index 00000000000..7fe7254ca0b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodLastIndexOf.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Luca Garulli + */ +public class OSQLMethodLastIndexOf extends OAbstractSQLMethod { + + public static final String NAME = "lastindexof"; + + public OSQLMethodLastIndexOf() { + super(NAME, 1, 2); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + final String toFind = OIOUtils.getStringContent(iParams[0].toString()); + return iParams.length > 1 ? iThis.toString().lastIndexOf(toFind, Integer.parseInt(iParams[1].toString())) : iThis.toString() + .lastIndexOf(toFind); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodNormalize.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodNormalize.java new file mode 100644 index 00000000000..47f11f610ec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodNormalize.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.io.OIOUtils; +import com.orientechnologies.common.util.OPatternConst; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.text.Normalizer; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodNormalize extends OAbstractSQLMethod { + + public static final String NAME = "normalize"; + + public OSQLMethodNormalize() { + super(NAME, 0, 2); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + + if (ioResult != null) { + final Normalizer.Form form = iParams != null && iParams.length > 0 ? Normalizer.Form.valueOf(OIOUtils + .getStringContent(iParams[0].toString())) : Normalizer.Form.NFD; + + String normalized = Normalizer.normalize(ioResult.toString(), form); + if (iParams != null && iParams.length > 1) { + normalized = normalized.replaceAll(OIOUtils.getStringContent(iParams[0].toString()), ""); + } else { + normalized = OPatternConst.PATTERN_DIACRITICAL_MARKS.matcher(normalized).replaceAll(""); + } + ioResult = normalized; + } + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodPrefix.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodPrefix.java new file mode 100644 index 00000000000..0d01a29746f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodPrefix.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodPrefix extends OAbstractSQLMethod { + + public static final String NAME = "prefix"; + + public OSQLMethodPrefix() { + super(NAME, 1); + } + + @Override + public Object execute(Object iThis, OIdentifiable iRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) + return iThis; + + return iParams[0] + iThis.toString(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemove.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemove.java new file mode 100644 index 00000000000..c54da4f84f8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemove.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Remove the first occurrence of elements from a collection. + * + * @see OSQLMethodRemoveAll + * + * @author Luca Garulli + */ +public class OSQLMethodRemove extends OAbstractSQLMethod { + + public static final String NAME = "remove"; + + public OSQLMethodRemove() { + super(NAME, 1, -1); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, Object ioResult, + Object[] iParams) { + if (iParams != null && iParams.length > 0 && iParams[0] != null) { + iParams = OMultiValue.array(iParams, Object.class, new OCallable() { + + @Override + public Object call(final Object iArgument) { + if (iArgument instanceof String && ((String) iArgument).startsWith("$")) { + return iContext.getVariable((String) iArgument); + } + return iArgument; + } + }); + for (Object o : iParams) { + ioResult = OMultiValue.remove(ioResult, o, false); + } + } + + return ioResult; + } + + @Override + public String getSyntax() { + return "remove(*)"; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemoveAll.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemoveAll.java new file mode 100644 index 00000000000..153dd70861b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodRemoveAll.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.common.util.OCallable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Remove all the occurrences of elements from a collection. + * + * @see OSQLMethodRemove + * + * @author Luca Garulli + */ +public class OSQLMethodRemoveAll extends OAbstractSQLMethod { + + public static final String NAME = "removeall"; + + public OSQLMethodRemoveAll() { + super(NAME, 1, -1); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, Object ioResult, + Object[] iParams) { + if (iParams != null && iParams.length > 0 && iParams[0] != null) { + iParams = OMultiValue.array(iParams, Object.class, new OCallable() { + + @Override + public Object call(final Object iArgument) { + if (iArgument instanceof String && ((String) iArgument).startsWith("$")) { + return iContext.getVariable((String) iArgument); + } + return iArgument; + } + }); + for (Object o : iParams) { + ioResult = OMultiValue.remove(ioResult, o, true); + } + } + + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSize.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSize.java new file mode 100755 index 00000000000..0b50a3927cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSize.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodSize extends OAbstractSQLMethod { + + public static final String NAME = "size"; + + public OSQLMethodSize() { + super(NAME); + } + + @Override + public Object execute(Object iThis, final OIdentifiable iCurrentRecord, final OCommandContext iContext, final Object ioResult, final Object[] iParams) { + + final Number size; + if (ioResult != null) { + if (ioResult instanceof OIdentifiable) { + size = 1; + } else { + size = OMultiValue.getSize(ioResult); + } + } else { + size = 0; + } + + return size; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSplit.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSplit.java new file mode 100644 index 00000000000..d81f5d04a36 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodSplit.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * Splits a string using a delimiter. + * + * @author Luca Garulli + */ +public class OSQLMethodSplit extends OAbstractSQLMethod { + + public static final String NAME = "split"; + + public OSQLMethodSplit() { + super(NAME, 1); + } + + @Override + public Object execute(Object iThis, OIdentifiable iRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null || iParams[0] == null) + return iThis; + + return iThis.toString().split(iParams[0].toString()); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToLowerCase.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToLowerCase.java new file mode 100644 index 00000000000..8282021b64a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToLowerCase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Locale; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodToLowerCase extends OAbstractSQLMethod { + + public static final String NAME = "tolowercase"; + + public OSQLMethodToLowerCase() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null ? ioResult.toString().toLowerCase(Locale.ENGLISH) : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToUpperCase.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToUpperCase.java new file mode 100644 index 00000000000..5dab5934de0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodToUpperCase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Locale; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodToUpperCase extends OAbstractSQLMethod { + + public static final String NAME = "touppercase"; + + public OSQLMethodToUpperCase() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null ? ioResult.toString().toUpperCase(Locale.ENGLISH) : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodTrim.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodTrim.java new file mode 100644 index 00000000000..52103c2de88 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodTrim.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodTrim extends OAbstractSQLMethod { + + public static final String NAME = "trim"; + + public OSQLMethodTrim() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null ? ioResult.toString().trim() : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodType.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodType.java new file mode 100755 index 00000000000..33b937d63cd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodType.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +/** + * Returns the value's OrientDB Type. + * + * @author Luca Garulli + */ +public class OSQLMethodType extends OAbstractSQLMethod { + + public static final String NAME = "type"; + + public OSQLMethodType() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (ioResult == null) { + return null; + } + final OType t = OType.getTypeByValue(ioResult); + + if (t != null) { + return t.toString(); + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodValues.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodValues.java new file mode 100644 index 00000000000..4a82b3cd346 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/misc/OSQLMethodValues.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.misc; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import java.util.Map; + +/** + * + * @author Johann Sorel (Geomatys) + * @author Luca Garulli + */ +public class OSQLMethodValues extends OAbstractSQLMethod { + + public static final String NAME = "values"; + + public OSQLMethodValues() { + super(NAME); + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + ioResult = ioResult != null && ioResult instanceof Map ? ((Map) ioResult).values() : null; + return ioResult; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodCurrent.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodCurrent.java new file mode 100644 index 00000000000..7fbe5c70263 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodCurrent.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.sequence; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns the current number of a sequence. + * + * @author Luca Garulli + */ +public class OSQLMethodCurrent extends OAbstractSQLMethod { + + public static final String NAME = "current"; + + public OSQLMethodCurrent() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "current()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis ==null) + throw new OCommandSQLParsingException("Method 'current()' can be invoked only on OSequence instances, while NULL was found"); + + if (!(iThis instanceof OSequence)) + throw new OCommandSQLParsingException("Method 'current()' can be invoked only on OSequence instances, while '" + + iThis.getClass() + "' was found"); + + return ((OSequence) iThis).current(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodNext.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodNext.java new file mode 100755 index 00000000000..4a2917e7cc8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodNext.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.sequence; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Returns the next number of a sequence. + * + * @author Luca Garulli + */ +public class OSQLMethodNext extends OAbstractSQLMethod { + + public static final String NAME = "next"; + + public OSQLMethodNext() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "next()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null) + throw new OCommandSQLParsingException("Method 'next()' can be invoked only on OSequence instances, while NULL was found"); + + if (!(iThis instanceof OSequence)) + throw new OCommandSQLParsingException("Method 'next()' can be invoked only on OSequence instances, while '" + + iThis.getClass() + "' was found"); + + return ((OSequence) iThis).next(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodReset.java b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodReset.java new file mode 100755 index 00000000000..453490e44a9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/method/sequence/OSQLMethodReset.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Orient Technologies. + * Copyright 2013 Geomatys. + * + * Licensed 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 com.orientechnologies.orient.core.sql.method.sequence; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.sequence.OSequence; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.method.misc.OAbstractSQLMethod; + +/** + * Reset a sequence. It returns the first sequence number after reset. + * + * @author Luca Garulli + */ +public class OSQLMethodReset extends OAbstractSQLMethod { + + public static final String NAME = "reset"; + + public OSQLMethodReset() { + super(NAME, 0, 0); + } + + @Override + public String getSyntax() { + return "reset()"; + } + + @Override + public Object execute(Object iThis, OIdentifiable iCurrentRecord, OCommandContext iContext, Object ioResult, Object[] iParams) { + if (iThis == null) + throw new OCommandSQLParsingException("Method 'reset()' can be invoked only on OSequence instances, while NULL was found"); + + if (!(iThis instanceof OSequence)) + throw new OCommandSQLParsingException("Method 'reset()' can be invoked only on OSequence instances, while '" + + iThis.getClass() + "' was found"); + + return ((OSequence) iThis).reset(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/ODefaultQueryOperatorFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/ODefaultQueryOperatorFactory.java new file mode 100644 index 00000000000..d923bc64c3a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/ODefaultQueryOperatorFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorDivide; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMinus; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMod; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMultiply; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorPlus; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Default operator factory. + * + * @author Johann Sorel (Geomatys) + */ +public class ODefaultQueryOperatorFactory implements OQueryOperatorFactory{ + + private static final Set OPERATORS; + + static { + final Set operators = new HashSet(); + operators.add(new OQueryOperatorEquals()); + operators.add(new OQueryOperatorAnd()); + operators.add(new OQueryOperatorOr()); + operators.add(new OQueryOperatorNotEquals()); + operators.add(new OQueryOperatorNotEquals2()); + operators.add(new OQueryOperatorNot()); + operators.add(new OQueryOperatorMinorEquals()); + operators.add(new OQueryOperatorMinor()); + operators.add(new OQueryOperatorMajorEquals()); + operators.add(new OQueryOperatorContainsAll()); + operators.add(new OQueryOperatorMajor()); + operators.add(new OQueryOperatorLike()); + operators.add(new OQueryOperatorMatches()); + operators.add(new OQueryOperatorInstanceof()); + operators.add(new OQueryOperatorIs()); + operators.add(new OQueryOperatorIn()); + operators.add(new OQueryOperatorContainsKey()); + operators.add(new OQueryOperatorContainsValue()); + operators.add(new OQueryOperatorContainsText()); + operators.add(new OQueryOperatorContains()); + operators.add(new OQueryOperatorTraverse()); + operators.add(new OQueryOperatorBetween()); + operators.add(new OQueryOperatorPlus()); + operators.add(new OQueryOperatorMinus()); + operators.add(new OQueryOperatorMultiply()); + operators.add(new OQueryOperatorDivide()); + operators.add(new OQueryOperatorMod()); + OPERATORS = Collections.unmodifiableSet(operators); + } + + /** + * {@inheritDoc} + */ + public Set getOperators() { + return OPERATORS; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OIndexReuseType.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OIndexReuseType.java new file mode 100644 index 00000000000..1b0c4b447a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OIndexReuseType.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +/** + * Represents hint how index can be used to calculate result of operator execution. + * + * @author Andrey Lomakin + * + * @see OQueryOperator#getIndexReuseType(Object, Object) + */ +public enum OIndexReuseType { + /** + * Results of this operator can be calculated as intersection of + * results for left and right operators. + */ + INDEX_INTERSECTION, + + /** + * Results of this operator can be calculated as union of + * results for left and right operators. + */ + INDEX_UNION, + + /** + * Index cna be used to calculate result of given operator. + */ + NO_INDEX, + + /** + * Result of execution of this operator can be replaced by call to one of + * {@link com.orientechnologies.orient.core.index.OIndex} methods. + */ + INDEX_METHOD, + /** + * Result of execution of this operator can be replaced by call to one of + * {@link com.orientechnologies.orient.core.index.OIndex} methods depending on user implementation. + */ + INDEX_OPERATOR +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperator.java new file mode 100755 index 00000000000..522e39531f2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperator.java @@ -0,0 +1,249 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.profiler.OProfiler; +import com.orientechnologies.orient.core.Orient; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OIndexSearchResult; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorDivide; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMinus; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMod; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorMultiply; +import com.orientechnologies.orient.core.sql.operator.math.OQueryOperatorPlus; + +import java.util.List; + +/** + * Query Operators. Remember to handle the operator in OQueryItemCondition. + * + * @author Luca Garulli + */ +public abstract class OQueryOperator { + + public static enum ORDER { + /** + * Used when order compared to other operator cannot be evaluated or has no consequences. + */ + UNKNOWNED, + /** + * Used when this operator must be before the other one + */ + BEFORE, + /** + * Used when this operator must be after the other one + */ + AFTER, + /** + * Used when this operator is equal the other one + */ + EQUAL + } + + /** + * Default operator order. can be used by additional operator to locate themself relatively to default ones. + *

          + * WARNING: ORDER IS IMPORTANT TO AVOID SUB-STRING LIKE "IS" and AND "INSTANCEOF": INSTANCEOF MUST BE PLACED BEFORE! AND ALSO FOR + * PERFORMANCE (MOST USED BEFORE) + */ + protected static final Class[] DEFAULT_OPERATORS_ORDER = { OQueryOperatorEquals.class, OQueryOperatorAnd.class, + OQueryOperatorOr.class, OQueryOperatorNotEquals.class, OQueryOperatorNotEquals2.class, OQueryOperatorNot.class, OQueryOperatorMinorEquals.class, + OQueryOperatorMinor.class, OQueryOperatorMajorEquals.class, OQueryOperatorContainsAll.class, OQueryOperatorMajor.class, + OQueryOperatorLike.class, OQueryOperatorMatches.class, OQueryOperatorInstanceof.class, OQueryOperatorIs.class, + OQueryOperatorIn.class, OQueryOperatorContainsKey.class, OQueryOperatorContainsValue.class, OQueryOperatorContainsText.class, + OQueryOperatorContains.class, OQueryOperatorTraverse.class, OQueryOperatorBetween.class, OQueryOperatorPlus.class, + OQueryOperatorMinus.class, OQueryOperatorMultiply.class, OQueryOperatorDivide.class, OQueryOperatorMod.class }; + + public final String keyword; + public final int precedence; + public final int expectedRightWords; + public final boolean unary; + public final boolean expectsParameters; + + protected OQueryOperator(final String iKeyword, final int iPrecedence, final boolean iUnary) { + this(iKeyword, iPrecedence, iUnary, 1, false); + } + + protected OQueryOperator(final String iKeyword, final int iPrecedence, final boolean iUnary, final int iExpectedRightWords) { + this(iKeyword, iPrecedence, iUnary, iExpectedRightWords, false); + } + + protected OQueryOperator(final String iKeyword, final int iPrecedence, final boolean iUnary, final int iExpectedRightWords, + final boolean iExpectsParameters) { + keyword = iKeyword; + precedence = iPrecedence; + unary = iUnary; + expectedRightWords = iExpectedRightWords; + expectsParameters = iExpectsParameters; + } + + public abstract Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, + final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight, OCommandContext iContext); + + /** + * Returns hint how index can be used to calculate result of operator execution. + * + * @param iLeft + * Value of left query parameter. + * @param iRight + * Value of right query parameter. + * @return Hint how index can be used to calculate result of operator execution. + */ + public abstract OIndexReuseType getIndexReuseType(Object iLeft, Object iRight); + + public OIndexSearchResult getOIndexSearchResult(OClass iSchemaClass, OSQLFilterCondition iCondition, + List iIndexSearchResults, OCommandContext context) { + + return null; + } + + /** + * Performs index query and returns index cursor which presents subset of index data which corresponds to result of execution of + * given operator. + * + *

          + * Query that should be executed can be presented like: [[property0 = keyParam0] and [property1 = keyParam1] and] propertyN + * operator keyParamN. + *

          + * It is supped that index which passed in as parameter is used to index properties listed above and responsibility of given + * method execute query using given parameters. + *

          + * Multiple parameters are passed in to implement composite indexes support. + * + * + * @param iContext + * @param index + * Instance of index that will be used to calculate result of operator execution. + * @param keyParams + * Parameters of query is used to calculate query result. + * @param ascSortOrder + * Data returned by cursors should be sorted in ascending or descending order. + * @return Cursor instance if index can be used to evaluate result of execution of given operator and null otherwise. + */ + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, final List keyParams, + boolean ascSortOrder) { + return null; + } + + @Override + public String toString() { + return keyword; + } + + /** + * Default State-less implementation: does not save parameters and just return itself + * + * @param iParams + * @return + */ + public OQueryOperator configure(final List iParams) { + return this; + } + + public String getSyntax() { + return " " + keyword + " "; + } + + public abstract ORID getBeginRidRange(final Object iLeft, final Object iRight); + + public abstract ORID getEndRidRange(final Object iLeft, final Object iRight); + + public boolean isUnary() { + return unary; + } + + /** + * Check priority of this operator compare to given operator. + * + * @param other + * @return ORDER place of this operator compared to given operator + */ + public ORDER compare(OQueryOperator other) { + final Class thisClass = this.getClass(); + final Class otherClass = other.getClass(); + + int thisPosition = -1; + int otherPosition = -1; + for (int i = 0; i < DEFAULT_OPERATORS_ORDER.length; i++) { + // subclass of default operators inherit their parent ordering + final Class clazz = DEFAULT_OPERATORS_ORDER[i]; + if (clazz.isAssignableFrom(thisClass)) { + thisPosition = i; + } + if (clazz.isAssignableFrom(otherClass)) { + otherPosition = i; + } + } + + if (thisPosition == -1 || otherPosition == -1) { + // cannot decide which comes first + return ORDER.UNKNOWNED; + } + + if (thisPosition > otherPosition) { + return ORDER.AFTER; + } else if (thisPosition < otherPosition) { + return ORDER.BEFORE; + } + + return ORDER.EQUAL; + } + + protected void updateProfiler(final OCommandContext iContext, final OIndex index, final List keyParams, + final OIndexDefinition indexDefinition) { + if (iContext.isRecordingMetrics()) + iContext.updateMetric("compositeIndexUsed", +1); + + final OProfiler profiler = Orient.instance().getProfiler(); + if (profiler.isRecording()) { + profiler.updateCounter(profiler.getDatabaseMetric(index.getDatabaseName(), "query.indexUsed"), "Used index in query", +1); + + int params = indexDefinition.getParamCount(); + if (params > 1) { + final String profiler_prefix = profiler.getDatabaseMetric(index.getDatabaseName(), "query.compositeIndexUsed"); + + profiler.updateCounter(profiler_prefix, "Used composite index in query", +1); + profiler.updateCounter(profiler_prefix + "." + params, "Used composite index in query with " + params + " params", +1); + profiler.updateCounter(profiler_prefix + "." + params + '.' + keyParams.size(), "Used composite index in query with " + + params + " params and " + keyParams.size() + " keys", +1); + } + } + } + + public boolean canShortCircuit(Object l) { + return false; + } + + public boolean canBeMerged() { + return true; + } + + public boolean isSupportingBinaryEvaluate() { + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorAnd.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorAnd.java new file mode 100644 index 00000000000..229720c193a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorAnd.java @@ -0,0 +1,112 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * AND operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorAnd extends OQueryOperator { + + public OQueryOperatorAnd() { + super("AND", 4, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + if (iLeft == null) + return false; + return (Boolean) iLeft && (Boolean) iRight; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iLeft == null || iRight == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_INTERSECTION; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + final ORID leftRange; + final ORID rightRange; + + if (iLeft instanceof OSQLFilterCondition) + leftRange = ((OSQLFilterCondition) iLeft).getBeginRidRange(); + else + leftRange = null; + + if (iRight instanceof OSQLFilterCondition) + rightRange = ((OSQLFilterCondition) iRight).getBeginRidRange(); + else + rightRange = null; + + if (leftRange == null && rightRange == null) + return null; + else if (leftRange == null) + return rightRange; + else if (rightRange == null) + return leftRange; + else + return leftRange.compareTo(rightRange) <= 0 ? rightRange : leftRange; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + final ORID leftRange; + final ORID rightRange; + + if (iLeft instanceof OSQLFilterCondition) + leftRange = ((OSQLFilterCondition) iLeft).getEndRidRange(); + else + leftRange = null; + + if (iRight instanceof OSQLFilterCondition) + rightRange = ((OSQLFilterCondition) iRight).getEndRidRange(); + else + rightRange = null; + + if (leftRange == null && rightRange == null) + return null; + else if (leftRange == null) + return rightRange; + else if (rightRange == null) + return leftRange; + else + return leftRange.compareTo(rightRange) >= 0 ? rightRange : leftRange; + } + + @Override + public boolean canShortCircuit(Object l) { + if (Boolean.FALSE.equals(l)) { + return true; + } + return false; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorBetween.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorBetween.java new file mode 100755 index 00000000000..8204db41d3b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorBetween.java @@ -0,0 +1,232 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * BETWEEN operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorBetween extends OQueryOperatorEqualityNotNulls { + private boolean leftInclusive = true; + private boolean rightInclusive = true; + + public OQueryOperatorBetween() { + super("BETWEEN", 5, false, 3); + } + + public boolean isLeftInclusive() { + return leftInclusive; + } + + public void setLeftInclusive(boolean leftInclusive) { + this.leftInclusive = leftInclusive; + } + + public boolean isRightInclusive() { + return rightInclusive; + } + + public void setRightInclusive(boolean rightInclusive) { + this.rightInclusive = rightInclusive; + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition condition, final Object left, + final Object right, OCommandContext iContext) { + validate(right); + + final Iterator valueIterator = OMultiValue.getMultiValueIterator(right, false); + + Object right1 = valueIterator.next(); + valueIterator.next(); + Object right2 = valueIterator.next(); + final Object right1c = OType.convert(right1, left.getClass()); + if (right1c == null) + return false; + + final Object right2c = OType.convert(right2, left.getClass()); + if (right2c == null) + return false; + + final int leftResult; + if (left instanceof Number && right1 instanceof Number) { + Number[] conv = OType.castComparableNumber((Number) left, (Number) right1); + leftResult = ((Comparable) conv[0]).compareTo(conv[1]); + } else { + leftResult = ((Comparable) left).compareTo(right1c); + } + final int rightResult; + if (left instanceof Number && right2 instanceof Number) { + Number[] conv = OType.castComparableNumber((Number) left, (Number) right2); + rightResult = ((Comparable) conv[0]).compareTo(conv[1]); + } else { + rightResult = ((Comparable) left).compareTo(right2c); + } + + return (leftInclusive ? leftResult >= 0 : leftResult > 0) && (rightInclusive ? rightResult <= 0 : rightResult < 0); + } + + private void validate(Object iRight) { + if (!OMultiValue.isMultiValue(iRight.getClass())) { + throw new IllegalArgumentException("Found '" + iRight + "' while was expected: " + getSyntax()); + } + + if (OMultiValue.getSize(iRight) != 3) + throw new IllegalArgumentException("Found '" + OMultiValue.toString(iRight) + "' while was expected: " + getSyntax()); + } + + @Override + public String getSyntax() { + return " " + keyword + " AND "; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + OIndexCursor cursor; + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators() || !internalIndex.hasRangeQuerySupport()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object[] betweenKeys = (Object[]) keyParams.get(0); + + final Object keyOne; + final Object keyTwo; + + if (indexDefinition instanceof OIndexDefinitionMultiValue) { + keyOne = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(OSQLHelper.getValue(betweenKeys[0])); + keyTwo = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(OSQLHelper.getValue(betweenKeys[2])); + } else { + keyOne = indexDefinition.createValue(Collections.singletonList(OSQLHelper.getValue(betweenKeys[0]))); + keyTwo = indexDefinition.createValue(Collections.singletonList(OSQLHelper.getValue(betweenKeys[2]))); + } + + if (keyOne == null || keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, leftInclusive, keyTwo, rightInclusive, ascSortOrder); + } else { + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object[] betweenKeys = (Object[]) keyParams.get(keyParams.size() - 1); + + final Object betweenKeyOne = OSQLHelper.getValue(betweenKeys[0]); + + if (betweenKeyOne == null) + return null; + + final Object betweenKeyTwo = OSQLHelper.getValue(betweenKeys[2]); + + if (betweenKeyTwo == null) + return null; + + final List betweenKeyOneParams = new ArrayList(keyParams.size()); + betweenKeyOneParams.addAll(keyParams.subList(0, keyParams.size() - 1)); + betweenKeyOneParams.add(betweenKeyOne); + + final List betweenKeyTwoParams = new ArrayList(keyParams.size()); + betweenKeyTwoParams.addAll(keyParams.subList(0, keyParams.size() - 1)); + betweenKeyTwoParams.add(betweenKeyTwo); + + final Object keyOne = compositeIndexDefinition.createSingleValue(betweenKeyOneParams); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(betweenKeyTwoParams); + + if (keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, leftInclusive, keyTwo, rightInclusive, ascSortOrder); + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + validate(iRight); + + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) { + final Iterator valueIterator = OMultiValue.getMultiValueIterator(iRight, false); + + final Object right1 = valueIterator.next(); + if (right1 != null) + return (ORID) right1; + + valueIterator.next(); + + return (ORID) valueIterator.next(); + } + + return null; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + validate(iRight); + + validate(iRight); + + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) { + final Iterator valueIterator = OMultiValue.getMultiValueIterator(iRight, false); + + final Object right1 = valueIterator.next(); + + valueIterator.next(); + + final Object right2 = valueIterator.next(); + + if (right2 == null) + return (ORID) right1; + + return (ORID) right2; + } + + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContains.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContains.java new file mode 100755 index 00000000000..daee9f2dc1a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContains.java @@ -0,0 +1,210 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * CONTAINS operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorContains extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorContains() { + super("CONTAINS", 5, false); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final OSQLFilterCondition condition; + if (iCondition.getLeft() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getLeft(); + else if (iCondition.getRight() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getRight(); + else + condition = null; + + if (iLeft instanceof Iterable) { + + final Iterable iterable = (Iterable) iLeft; + + if (condition != null) { + // CHECK AGAINST A CONDITION + for (final Object o : iterable) { + final OIdentifiable id; + if (o instanceof OIdentifiable) + id = (OIdentifiable) o; + else if (o instanceof Map) { + final Iterator iter = ((Map) o).values().iterator(); + final Object v = iter.hasNext() ? iter.next() : null; + if (v instanceof OIdentifiable) + id = (OIdentifiable) v; + else + // TRANSFORM THE ENTIRE MAP IN A DOCUMENT. PROBABLY HAS BEEN IMPORTED FROM JSON + id = new ODocument((Map) o); + + } else if (o instanceof Iterable) { + final Iterator iter = ((Iterable) o).iterator(); + id = iter.hasNext() ? iter.next() : null; + } else + continue; + + if ((Boolean) condition.evaluate(id, null, iContext) == Boolean.TRUE) + return true; + } + } else { + // CHECK AGAINST A SINGLE VALUE + OType type =null; + + if(iCondition.getLeft() instanceof OSQLFilterItemField && ((OSQLFilterItemField) iCondition.getLeft()).isFieldChain() && ((OSQLFilterItemField) iCondition.getLeft()).getFieldChain().getItemCount()==1){ + String fieldName = ((OSQLFilterItemField) iCondition.getLeft()).getFieldChain().getItemName(0); + if(fieldName!=null) { + Object record = iRecord.getRecord(); + if (record instanceof ODocument) { + OProperty property = ((ODocument) record).getSchemaClass() + .getProperty(fieldName); + if(property!=null && property.getType().isMultiValue()){ + type = property.getLinkedType(); + } + } + } + } + for (final Object o : iterable) { + if (OQueryOperatorEquals.equals(iRight, o, type)) + return true; + } + } + } else if (iRight instanceof Iterable) { + + // CHECK AGAINST A CONDITION + final Iterable iterable = (Iterable) iRight; + + if (condition != null) { + for (final OIdentifiable o : iterable) { + if ((Boolean) condition.evaluate(o, null, iContext) == Boolean.TRUE) + return true; + } + } else { + // CHECK AGAINST A SINGLE VALUE + for (final Object o : iterable) { + if (OQueryOperatorEquals.equals(iLeft, o)) + return true; + } + } + } + return false; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (!(iLeft instanceof OSQLFilterCondition) && !(iRight instanceof OSQLFilterCondition)) + return OIndexReuseType.INDEX_METHOD; + + return OIndexReuseType.NO_INDEX; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + OIndexCursor cursor; + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + final Object indexResult; + + indexResult = index.get(key); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else { + // in case of composite keys several items can be returned in case of we perform search + // using part of composite key stored in index. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + if (internalIndex.hasRangeQuerySupport()) { + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } else { + int indexParamCount = indexDefinition.getParamCount(); + if (indexParamCount == keyParams.size()) { + final Object indexResult; + indexResult = index.get(keyOne); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, keyOne); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, keyOne); + } else + return null; + } + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsAll.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsAll.java new file mode 100644 index 00000000000..68e2682a1d7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsAll.java @@ -0,0 +1,134 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import java.util.Collection; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * CONTAINS ALL operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorContainsAll extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorContainsAll() { + super("CONTAINSALL", 5, false); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final OSQLFilterCondition condition; + + if (iCondition.getLeft() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getLeft(); + else if (iCondition.getRight() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getRight(); + else + condition = null; + + if (iLeft.getClass().isArray()) { + if (iRight.getClass().isArray()) { + // ARRAY VS ARRAY + int matches = 0; + for (final Object l : (Object[]) iLeft) { + for (final Object r : (Object[]) iRight) { + if (OQueryOperatorEquals.equals(l, r)) { + ++matches; + break; + } + } + } + return matches == ((Object[]) iRight).length; + } else if (iRight instanceof Collection) { + // ARRAY VS ARRAY + int matches = 0; + for (final Object l : (Object[]) iLeft) { + for (final Object r : (Collection) iRight) { + if (OQueryOperatorEquals.equals(l, r)) { + ++matches; + break; + } + } + } + return matches == ((Collection) iRight).size(); + } + + } else if (iLeft instanceof Collection) { + + final Collection collection = (Collection) iLeft; + + if (condition != null) { + // CHECK AGAINST A CONDITION + for (final ODocument o : collection) { + if ((Boolean) condition.evaluate(o, null, iContext) == Boolean.FALSE) + return false; + } + } else { + // CHECK AGAINST A SINGLE VALUE + for (final Object o : collection) { + if (!OQueryOperatorEquals.equals(iRight, o)) + return false; + } + } + } else if (iRight instanceof Collection) { + + // CHECK AGAINST A CONDITION + final Collection collection = (Collection) iRight; + + if (condition != null) { + for (final ODocument o : collection) { + if ((Boolean) condition.evaluate(o, null, iContext) == Boolean.FALSE) + return false; + } + } else { + // CHECK AGAINST A SINGLE VALUE + for (final Object o : collection) { + if (!OQueryOperatorEquals.equals(iLeft, o)) + return false; + } + } + } + return true; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsKey.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsKey.java new file mode 100755 index 00000000000..ef16ca29a4e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsKey.java @@ -0,0 +1,141 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexCursorCollectionValue; +import com.orientechnologies.orient.core.index.OIndexCursorSingleValue; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.index.OPropertyMapIndexDefinition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * CONTAINS KEY operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorContainsKey extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorContainsKey() { + super("CONTAINSKEY", 5, false); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + + if (iLeft instanceof Map) { + + final Map map = (Map) iLeft; + return map.containsKey(iRight); + } else if (iRight instanceof Map) { + + final Map map = (Map) iRight; + return map.containsKey(iLeft); + } + return false; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + OIndexCursor cursor; + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + if (!((indexDefinition instanceof OPropertyMapIndexDefinition) && ((OPropertyMapIndexDefinition) indexDefinition) + .getIndexBy() == OPropertyMapIndexDefinition.INDEX_BY.KEY)) + return null; + + final Object key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + + if (key == null) + return null; + + final Object indexResult = index.get(key); + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else { + // in case of composite keys several items can be returned in case of we perform search + // using part of composite key stored in index. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + if (!((compositeIndexDefinition.getMultiValueDefinition() instanceof OPropertyMapIndexDefinition) && ((OPropertyMapIndexDefinition) compositeIndexDefinition + .getMultiValueDefinition()).getIndexBy() == OPropertyMapIndexDefinition.INDEX_BY.KEY)) + return null; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + if (internalIndex.hasRangeQuerySupport()) { + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } else { + if (indexDefinition.getParamCount() == keyParams.size()) { + final Object indexResult = index.get(keyOne); + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, keyOne); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, keyOne); + } else + return null; + } + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsText.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsText.java new file mode 100644 index 00000000000..cdb9b3e293e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsText.java @@ -0,0 +1,161 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import java.util.Collection; +import java.util.List; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexCursorCollectionValue; +import com.orientechnologies.orient.core.index.OIndexCursorSingleValue; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexFullText; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; + +/** + * CONTAINSTEXT operator. Look if a text is contained in a property. This is usually used with the FULLTEXT-INDEX for fast lookup at + * piece of text. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorContainsText extends OQueryTargetOperator { + private boolean ignoreCase = true; + + public OQueryOperatorContainsText(final boolean iIgnoreCase) { + super("CONTAINSTEXT", 5, false); + ignoreCase = iIgnoreCase; + } + + public OQueryOperatorContainsText() { + super("CONTAINSTEXT", 5, false); + } + + @Override + public String getSyntax() { + return " CONTAINSTEXT[( noignorecase ] )] "; + } + + /** + * This is executed on non-indexed fields. + */ + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + if (iLeft == null || iRight == null) + return false; + + return iLeft.toString().indexOf(iRight.toString()) > -1; + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + @Override + public Collection filterRecords(final ODatabase iDatabase, final List iTargetClasses, + final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight) { + + final String fieldName; + if (iCondition.getLeft() instanceof OSQLFilterItemField) + fieldName = iCondition.getLeft().toString(); + else + fieldName = iCondition.getRight().toString(); + + final String fieldValue; + if (iCondition.getLeft() instanceof OSQLFilterItemField) + fieldValue = iCondition.getRight().toString(); + else + fieldValue = iCondition.getLeft().toString(); + + final String className = iTargetClasses.get(0); + + final OProperty prop = ((OMetadataInternal) iDatabase.getMetadata()).getImmutableSchemaSnapshot().getClass(className) + .getProperty(fieldName); + if (prop == null) + // NO PROPERTY DEFINED + return null; + + OIndex fullTextIndex = null; + for (final OIndex indexDefinition : prop.getIndexes()) { + if (indexDefinition instanceof OIndexFullText) { + fullTextIndex = indexDefinition; + break; + } + } + + if (fullTextIndex == null) { + return null; + } + + return (Collection) fullTextIndex.get(fieldValue); + } + + public boolean isIgnoreCase() { + return ignoreCase; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + + final OIndexDefinition indexDefinition = index.getDefinition(); + if (indexDefinition.getParamCount() > 1) + return null; + + final OIndex internalIndex = index.getInternal(); + + OIndexCursor cursor; + if (internalIndex instanceof OIndexFullText) { + final Object key = indexDefinition.createValue(keyParams); + final Object indexResult = index.get(key); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else + return null; + + updateProfiler(iContext, internalIndex, keyParams, indexDefinition); + + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsValue.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsValue.java new file mode 100755 index 00000000000..b7ef5ed4604 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorContainsValue.java @@ -0,0 +1,203 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.exception.OException; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.ODatabaseException; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OProperty; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * CONTAINS KEY operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorContainsValue extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorContainsValue() { + super("CONTAINSVALUE", 5, false); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (!(iRight instanceof OSQLFilterCondition) && !(iLeft instanceof OSQLFilterCondition)) + return OIndexReuseType.INDEX_METHOD; + + return OIndexReuseType.NO_INDEX; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + OIndexCursor cursor; + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + if (!((indexDefinition instanceof OPropertyMapIndexDefinition) && ((OPropertyMapIndexDefinition) indexDefinition) + .getIndexBy() == OPropertyMapIndexDefinition.INDEX_BY.VALUE)) + return null; + + final Object key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + + if (key == null) + return null; + + final Object indexResult = index.get(key); + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else { + // in case of composite keys several items can be returned in case of we perform search + // using part of composite key stored in index. + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + if (!((compositeIndexDefinition.getMultiValueDefinition() instanceof OPropertyMapIndexDefinition) && ((OPropertyMapIndexDefinition) compositeIndexDefinition + .getMultiValueDefinition()).getIndexBy() == OPropertyMapIndexDefinition.INDEX_BY.VALUE)) + return null; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + if (internalIndex.hasRangeQuerySupport()) { + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } else { + if (indexDefinition.getParamCount() == keyParams.size()) { + final Object indexResult = index.get(keyOne); + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, keyOne); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, keyOne); + } else + return null; + } + + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final OSQLFilterCondition condition; + if (iCondition.getLeft() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getLeft(); + else if (iCondition.getRight() instanceof OSQLFilterCondition) + condition = (OSQLFilterCondition) iCondition.getRight(); + else + condition = null; + + OType type = null; + if (iCondition.getLeft() instanceof OSQLFilterItemField && ((OSQLFilterItemField) iCondition.getLeft()).isFieldChain() + && ((OSQLFilterItemField) iCondition.getLeft()).getFieldChain().getItemCount() == 1) { + String fieldName = ((OSQLFilterItemField) iCondition.getLeft()).getFieldChain().getItemName(0); + if (fieldName != null) { + Object record = iRecord.getRecord(); + if (record instanceof ODocument) { + OProperty property = ((ODocument) record).getSchemaClass().getProperty(fieldName); + if (property != null && property.getType().isMultiValue()) { + type = property.getLinkedType(); + } + } + } + } + + Object right = iRight; + if (type != null) { + right = OType.convert(iRight, type.getDefaultJavaType()); + } + + if (iLeft instanceof Map) { + final Map map = (Map) iLeft; + + if (condition != null) { + // CHECK AGAINST A CONDITION + for (Object o : map.values()) { + o = loadIfNeed(o); + if ((Boolean) condition.evaluate((ODocument) o, null, iContext)) + return true; + } + } else + return map.containsValue(right); + + } else if (iRight instanceof Map) { + final Map map = (Map) iRight; + + if (condition != null) + // CHECK AGAINST A CONDITION + for (Object o : map.values()) { + o = loadIfNeed(o); + if ((Boolean) condition.evaluate((ODocument) o, null, iContext)) + return true; + else + return map.containsValue(iLeft); + } + } + return false; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Object loadIfNeed(Object o) { + final ORecord record = (ORecord) o; + if (record.getRecord().getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) { + try { + o = record. load(); + } catch (ORecordNotFoundException e) { + throw OException.wrapException(new ODatabaseException("Error during loading record with id : " + record.getIdentity()), e); + } + } + return o; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquality.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquality.java new file mode 100755 index 00000000000..213b4513cbb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquality.java @@ -0,0 +1,159 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.collate.OCollate; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.query.OQueryRuntimeValueMulti; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemFieldAll; + +/** + * Base equality operator. It's an abstract class able to compare the equality between two values. + * + * @author Luca Garulli + * + */ +public abstract class OQueryOperatorEquality extends OQueryOperator { + + protected OQueryOperatorEquality(final String iKeyword, final int iPrecedence, final boolean iLogical) { + super(iKeyword, iPrecedence, false); + } + + protected OQueryOperatorEquality(final String iKeyword, final int iPrecedence, final boolean iLogical, + final int iExpectedRightWords) { + super(iKeyword, iPrecedence, false, iExpectedRightWords); + } + + protected OQueryOperatorEquality(final String iKeyword, final int iPrecedence, final boolean iLogical, + final int iExpectedRightWords, final boolean iExpectsParameters) { + super(iKeyword, iPrecedence, iLogical, iExpectedRightWords, iExpectsParameters); + } + + protected abstract boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext); + + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, final OCommandContext iContext) { + final Object left = ORecordSerializerBinary.INSTANCE.getCurrentSerializer().deserializeValue(iFirstField.bytes, + iFirstField.type, null); + final Object right = ORecordSerializerBinary.INSTANCE.getCurrentSerializer().deserializeValue(iSecondField.bytes, + iFirstField.type, null); + + return evaluateExpression(null, null, left, right, iContext); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + + if (iLeft instanceof OBinaryField && iRight instanceof OBinaryField) + // BINARY COMPARISON + return evaluate((OBinaryField) iLeft, (OBinaryField) iRight, iContext); + else if (iLeft instanceof OQueryRuntimeValueMulti) { + // LEFT = MULTI + final OQueryRuntimeValueMulti left = (OQueryRuntimeValueMulti) iLeft; + + if (left.getValues().length == 0) + return false; + + if (left.getDefinition().getRoot().startsWith(OSQLFilterItemFieldAll.NAME)) { + // ALL VALUES + for (int i = 0; i < left.getValues().length; ++i) { + Object v = left.getValues()[i]; + Object r = iRight; + + final OCollate collate = left.getCollate(i); + if (collate != null) { + v = collate.transform(v); + r = collate.transform(iRight); + } + + if (v == null || !evaluateExpression(iRecord, iCondition, v, r, iContext)) + return false; + } + return true; + } else { + // ANY VALUES + for (int i = 0; i < left.getValues().length; ++i) { + Object v = left.getValues()[i]; + Object r = iRight; + + final OCollate collate = left.getCollate(i); + if (collate != null) { + v = collate.transform(v); + r = collate.transform(iRight); + } + + if (v != null && evaluateExpression(iRecord, iCondition, v, r, iContext)) + return true; + } + return false; + } + + } else if (iRight instanceof OQueryRuntimeValueMulti) { + // RIGHT = MULTI + final OQueryRuntimeValueMulti right = (OQueryRuntimeValueMulti) iRight; + + if (right.getValues().length == 0) + return false; + + if (right.getDefinition().getRoot().startsWith(OSQLFilterItemFieldAll.NAME)) { + // ALL VALUES + for (int i = 0; i < right.getValues().length; ++i) { + Object v = right.getValues()[i]; + Object l = iLeft; + + final OCollate collate = right.getCollate(i); + if (collate != null) { + v = collate.transform(v); + l = collate.transform(iLeft); + } + + if (v == null || !evaluateExpression(iRecord, iCondition, l, v, iContext)) + return false; + } + return true; + } else { + // ANY VALUES + for (int i = 0; i < right.getValues().length; ++i) { + Object v = right.getValues()[i]; + Object l = iLeft; + + final OCollate collate = right.getCollate(i); + if (collate != null) { + v = collate.transform(v); + l = collate.transform(iLeft); + } + + if (v != null && evaluateExpression(iRecord, iCondition, l, v, iContext)) + return true; + } + return false; + } + } else { + // SINGLE SIMPLE ITEM + return evaluateExpression(iRecord, iCondition, iLeft, iRight, iContext); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEqualityNotNulls.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEqualityNotNulls.java new file mode 100644 index 00000000000..866dade8084 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEqualityNotNulls.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * Base equality operator that not admit NULL in the LEFT and in the RIGHT operands. Abstract class. + * + * @author Luca Garulli + * + */ +public abstract class OQueryOperatorEqualityNotNulls extends OQueryOperatorEquality { + + protected OQueryOperatorEqualityNotNulls(final String iKeyword, final int iPrecedence, final boolean iLogical) { + super(iKeyword, iPrecedence, iLogical); + } + + protected OQueryOperatorEqualityNotNulls(final String iKeyword, final int iPrecedence, final boolean iLogical, + final int iExpectedRightWords) { + super(iKeyword, iPrecedence, iLogical, iExpectedRightWords); + } + + protected OQueryOperatorEqualityNotNulls(final String iKeyword, final int iPrecedence, final boolean iUnary, + final int iExpectedRightWords, final boolean iExpectsParameters) { + super(iKeyword, iPrecedence, iUnary, iExpectedRightWords, iExpectsParameters); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + if (iLeft == null || iRight == null) + return false; + + return super.evaluateRecord(iRecord, iCurrentResult, iCondition, iLeft, iRight, iContext); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquals.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquals.java new file mode 100755 index 00000000000..592f71827b6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorEquals.java @@ -0,0 +1,239 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * EQUALS operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorEquals extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate = false; + + public OQueryOperatorEquals() { + super("=", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + public static boolean equals(final Object iLeft, final Object iRight, OType type) { + if (type == null) { + return equals(iLeft, iRight); + } + Object left = OType.convert(iLeft, type.getDefaultJavaType()); + Object right = OType.convert(iRight, type.getDefaultJavaType()); + return equals(left, right); + } + + public static boolean equals(final Object iLeft, final Object iRight) { + if (iLeft == null || iRight == null) + return false; + + if (iLeft == iRight) { + return true; + } + + // RECORD & ORID + if (iLeft instanceof ORecord) + return comparesValues(iRight, (ORecord) iLeft, true); + else if (iRight instanceof ORecord) + return comparesValues(iLeft, (ORecord) iRight, true); + + // NUMBERS + if (iLeft instanceof Number && iRight instanceof Number) { + Number[] couple = OType.castComparableNumber((Number) iLeft, (Number) iRight); + return couple[0].equals(couple[1]); + } + + // ALL OTHER CASES + try { + final Object right = OType.convert(iRight, iLeft.getClass()); + + if (right == null) + return false; + if (iLeft instanceof byte[] && iRight instanceof byte[]) { + return Arrays.equals((byte[]) iLeft, (byte[]) iRight); + } + return iLeft.equals(right); + } catch (Exception e) { + return false; + } + } + + protected static boolean comparesValues(final Object iValue, final ORecord iRecord, final boolean iConsiderIn) { + // ORID && RECORD + final ORID other = ((ORecord) iRecord).getIdentity(); + + if (!other.isPersistent() && iRecord instanceof ODocument) { + // ODOCUMENT AS RESULT OF SUB-QUERY: GET THE FIRST FIELD IF ANY + final String[] firstFieldName = ((ODocument) iRecord).fieldNames(); + if (firstFieldName.length > 0) { + Object fieldValue = ((ODocument) iRecord).field(firstFieldName[0]); + if (fieldValue != null) { + if (iConsiderIn && OMultiValue.isMultiValue(fieldValue)) { + for (Object o : OMultiValue.getMultiValueIterable(fieldValue, false)) { + if (o != null && o.equals(iValue)) + return true; + } + } + + return fieldValue.equals(iValue); + } + } + return false; + } + return other.equals(iValue); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iLeft instanceof OIdentifiable && iRight instanceof OIdentifiable) + return OIndexReuseType.NO_INDEX; + if (iRight == null || iLeft == null) + return OIndexReuseType.NO_INDEX; + + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + OIndexCursor cursor; + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + final Object indexResult; + indexResult = index.get(key); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else { + // in case of composite keys several items can be returned in case of we perform search + // using part of composite key stored in index. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + + if (internalIndex.hasRangeQuerySupport()) { + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } else { + if (indexDefinition.getParamCount() == keyParams.size()) { + final Object indexResult; + indexResult = index.get(keyOne); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, keyOne); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, keyOne); + } else + return null; + } + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) + if (iRight instanceof ORID) + return (ORID) iRight; + else { + if (iRight instanceof OSQLFilterItemParameter && ((OSQLFilterItemParameter) iRight) + .getValue(null, null, null) instanceof ORID) + return (ORID) ((OSQLFilterItemParameter) iRight).getValue(null, null, null); + } + + if (iRight instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iRight).getRoot())) + if (iLeft instanceof ORID) + return (ORID) iLeft; + else { + if (iLeft instanceof OSQLFilterItemParameter && ((OSQLFilterItemParameter) iLeft) + .getValue(null, null, null) instanceof ORID) + return (ORID) ((OSQLFilterItemParameter) iLeft).getValue(null, null, null); + } + + return null; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + return getBeginRidRange(iLeft, iRight); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + return equals(iLeft, iRight); + } + + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isEqual(iFirstField, iSecondField); + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorFactory.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorFactory.java new file mode 100644 index 00000000000..2ca26a2b9a4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012 Orient Technologies. + * + * Licensed 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 com.orientechnologies.orient.core.sql.operator; + +import java.util.Set; + +/** + * Factory to register new OqueryOperators. + * + * @author Johann Sorel (Geomatys) + */ +public interface OQueryOperatorFactory { + + /** + * @return set of operators + */ + Set getOperators(); + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIn.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIn.java new file mode 100644 index 00000000000..4f2098a9224 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIn.java @@ -0,0 +1,300 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; +import com.orientechnologies.orient.core.sql.query.OResultSet; + +import java.util.*; + +/** + * IN operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorIn extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorIn() { + super("IN", 5, false); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.INDEX_METHOD; + } + + @SuppressWarnings("unchecked") + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + OIndexCursor cursor; + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object inKeyValue = keyParams.get(0); + Collection inParams; + if (inKeyValue instanceof List) + inParams = (Collection) inKeyValue; + else if (inKeyValue instanceof OSQLFilterItem) + inParams = (Collection) ((OSQLFilterItem) inKeyValue).getValue(null, null, iContext); + else + inParams = Collections.singleton(inKeyValue); + + if (inParams == null) { + return null; + } + if (inParams instanceof OResultSet) {//manage IN (subquery) + Set newInParams = new HashSet(); + for (Object o : ((OResultSet) inParams)) { + if (o instanceof ODocument && ((ODocument) o).getIdentity().getClusterId() < -1) { + ODocument doc = (ODocument) o; + String[] fieldNames = doc.fieldNames(); + if (fieldNames.length == 1) { + newInParams.add(doc.field(fieldNames[0])); + } else { + newInParams.add(o); + } + } else { + newInParams.add(o); + } + } + inParams = newInParams; + } + final List inKeys = new ArrayList(); + + boolean containsNotCompatibleKey = false; + for (final Object keyValue : inParams) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(OSQLHelper.getValue(keyValue)); + else + key = indexDefinition.createValue(OSQLHelper.getValue(keyValue)); + + if (key == null) { + containsNotCompatibleKey = true; + break; + } + + inKeys.add(key); + + } + if (containsNotCompatibleKey) + return null; + + cursor = index.iterateEntries(inKeys, ascSortOrder); + } else { + final List partialKey = new ArrayList(); + partialKey.addAll(keyParams); + partialKey.remove(keyParams.size() - 1); + + final Object inKeyValue = keyParams.get(keyParams.size() - 1); + + final Collection inParams; + if (inKeyValue instanceof List) + inParams = (Collection) inKeyValue; + else if (inKeyValue instanceof OSQLFilterItem) + inParams = (Collection) ((OSQLFilterItem) inKeyValue).getValue(null, null, iContext); + else + throw new IllegalArgumentException("Key '" + inKeyValue + "' is not valid"); + + final List inKeys = new ArrayList(); + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + boolean containsNotCompatibleKey = false; + for (final Object keyValue : inParams) { + List fullKey = new ArrayList(); + fullKey.addAll(partialKey); + fullKey.add(keyValue); + final Object key = compositeIndexDefinition.createSingleValue(fullKey); + if (key == null) { + containsNotCompatibleKey = true; + break; + } + + inKeys.add(key); + + } + if (containsNotCompatibleKey) { + return null; + } + + if (inKeys == null) + return null; + + if (indexDefinition.getParamCount() == keyParams.size()) { + final Object indexResult; + indexResult = index.iterateEntries(inKeys, ascSortOrder); + + if (indexResult == null || indexResult instanceof OIdentifiable) { + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, inKeys); + } else if (indexResult instanceof OIndexCursor) { + cursor = (OIndexCursor) indexResult; + } else { + cursor = new OIndexCursorCollectionValue((Collection) indexResult, inKeys); + } + } else + return null; + } + + updateProfiler(iContext, internalIndex, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + final Iterable ridCollection; + final int ridSize; + if (iRight instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iRight).getRoot())) { + if (iLeft instanceof OSQLFilterItem) + iLeft = ((OSQLFilterItem) iLeft).getValue(null, null, null); + + ridCollection = OMultiValue.getMultiValueIterable(iLeft); + ridSize = OMultiValue.getSize(iLeft); + } else if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID + .equals(((OSQLFilterItemField) iLeft).getRoot())) { + if (iRight instanceof OSQLFilterItem) + iRight = ((OSQLFilterItem) iRight).getValue(null, null, null); + ridCollection = OMultiValue.getMultiValueIterable(iRight); + ridSize = OMultiValue.getSize(iRight); + } else + return null; + + final List rids = addRangeResults(ridCollection, ridSize); + + return rids == null ? null : Collections.min(rids); + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + final Iterable ridCollection; + final int ridSize; + if (iRight instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iRight).getRoot())) { + if (iLeft instanceof OSQLFilterItem) + iLeft = ((OSQLFilterItem) iLeft).getValue(null, null, null); + + ridCollection = OMultiValue.getMultiValueIterable(iLeft, false); + ridSize = OMultiValue.getSize(iLeft); + } else if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID + .equals(((OSQLFilterItemField) iLeft).getRoot())) { + if (iRight instanceof OSQLFilterItem) + iRight = ((OSQLFilterItem) iRight).getValue(null, null, null); + + ridCollection = OMultiValue.getMultiValueIterable(iRight, false); + ridSize = OMultiValue.getSize(iRight); + } else + return null; + + final List rids = addRangeResults(ridCollection, ridSize); + + return rids == null ? null : Collections.max(rids); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + if (OMultiValue.isMultiValue(iLeft)) { + if (iRight instanceof Collection) { + // AGAINST COLLECTION OF ITEMS + final Collection collectionToMatch = (Collection) iRight; + + boolean found = false; + for (final Object o1 : OMultiValue.getMultiValueIterable(iLeft, false)) { + for (final Object o2 : collectionToMatch) { + if (OQueryOperatorEquals.equals(o1, o2)) { + found = true; + break; + } + } + } + return found; + } else { + // AGAINST SINGLE ITEM + if (iLeft instanceof Set) + return ((Set) iLeft).contains(iRight); + + for (final Object o : OMultiValue.getMultiValueIterable(iLeft, false)) { + if (OQueryOperatorEquals.equals(iRight, o)) + return true; + } + } + } else if (OMultiValue.isMultiValue(iRight)) { + + if (iRight instanceof Set) + return ((Set) iRight).contains(iLeft); + + for (final Object o : OMultiValue.getMultiValueIterable(iRight, false)) { + if (OQueryOperatorEquals.equals(iLeft, o)) + return true; + } + } else if (iLeft.getClass().isArray()) { + + for (final Object o : (Object[]) iLeft) { + if (OQueryOperatorEquals.equals(iRight, o)) + return true; + } + } else if (iRight.getClass().isArray()) { + + for (final Object o : (Object[]) iRight) { + if (OQueryOperatorEquals.equals(iLeft, o)) + return true; + } + } + + return iLeft.equals(iRight); + } + + protected List addRangeResults(final Iterable ridCollection, final int ridSize) { + if (ridCollection == null) + return null; + + List rids = null; + for (Object rid : ridCollection) { + if (rid instanceof OSQLFilterItemParameter) + rid = ((OSQLFilterItemParameter) rid).getValue(null, null, null); + + if (rid instanceof OIdentifiable) { + final ORID r = ((OIdentifiable) rid).getIdentity(); + if (r.isPersistent()) { + if (rids == null) + // LAZY CREATE IT + rids = new ArrayList(ridSize); + rids.add(r); + } + } + } + return rids; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorInstanceof.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorInstanceof.java new file mode 100644 index 00000000000..9d600ea8ecb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorInstanceof.java @@ -0,0 +1,86 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.OMetadataInternal; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.record.impl.ODocumentInternal; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * EQUALS operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorInstanceof extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorInstanceof() { + super("INSTANCEOF", 5, false); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + + final OSchema schema = ((OMetadataInternal)ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata()).getImmutableSchemaSnapshot(); + + final String baseClassName = iRight.toString(); + final OClass baseClass = schema.getClass(baseClassName); + if (baseClass == null) + throw new OCommandExecutionException("Class '" + baseClassName + "' is not defined in database schema"); + + OClass cls = null; + if (iLeft instanceof OIdentifiable) { + // GET THE RECORD'S CLASS + final ORecord record = ((OIdentifiable) iLeft).getRecord(); + if (record instanceof ODocument) { + cls = ODocumentInternal.getImmutableSchemaClass(((ODocument) record)); + } + } else if (iLeft instanceof String) + // GET THE CLASS BY NAME + cls = schema.getClass((String) iLeft); + + return cls != null ? cls.isSubClassOf(baseClass) : false; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIs.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIs.java new file mode 100644 index 00000000000..0c6efd57226 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorIs.java @@ -0,0 +1,145 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexCursorCollectionValue; +import com.orientechnologies.orient.core.index.OIndexCursorSingleValue; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OSQLHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +import java.util.Collection; +import java.util.List; + +/** + * IS operator. Different by EQUALS since works also for null. Example "IS null" + * + * @author Luca Garulli + * + */ +public class OQueryOperatorIs extends OQueryOperatorEquality { + + public OQueryOperatorIs() { + super("IS", 5, false); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + if (OSQLHelper.NOT_NULL.equals(iRight)) + return iLeft != null; + else if (OSQLHelper.NOT_NULL.equals(iLeft)) + return iRight != null; + else if (OSQLHelper.DEFINED.equals(iLeft)) + return evaluateDefined(iRecord, (String) iRight); + else if (OSQLHelper.DEFINED.equals(iRight)) + return evaluateDefined(iRecord, (String) iLeft); + else + return iLeft == iRight; + } + + protected boolean evaluateDefined(final OIdentifiable iRecord, final String iFieldName) { + if (iRecord instanceof ODocument) { + return ((ODocument) iRecord).containsField(iFieldName); + } + return false; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iRight == null) + return OIndexReuseType.INDEX_METHOD; + + return OIndexReuseType.NO_INDEX; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + OIndexCursor cursor; + if (!internalIndex.canBeUsedInEqualityOperators()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + final Object indexResult; + indexResult = index.get(key); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, key); + } else { + // in case of composite keys several items can be returned in case we perform search + // using part of composite key stored in index + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + + if (internalIndex.hasRangeQuerySupport()) { + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } else { + if (indexDefinition.getParamCount() == keyParams.size()) { + final Object indexResult; + indexResult = index.get(keyOne); + + if (indexResult == null || indexResult instanceof OIdentifiable) + cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, keyOne); + else + cursor = new OIndexCursorCollectionValue((Collection) indexResult, keyOne); + } else + return null; + } + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorLike.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorLike.java new file mode 100644 index 00000000000..c6c6a8fc5d6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorLike.java @@ -0,0 +1,64 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.query.OQueryHelper; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * LIKE operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorLike extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorLike() { + super("LIKE", 5, false); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + if (OMultiValue.isMultiValue(iLeft) || OMultiValue.isMultiValue(iRight)) + return false; + + return OQueryHelper.like(iLeft.toString(), iRight.toString()); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajor.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajor.java new file mode 100755 index 00000000000..72eb768474e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajor.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; + +import java.util.List; + +/** + * MAJOR operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMajor extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate=false; + + public OQueryOperatorMajor() { + super(">", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final Object right = OType.convert(iRight, iLeft.getClass()); + if (right == null) + return false; + return ((Comparable) iLeft).compareTo(right) > 0; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iRight == null || iLeft == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + OIndexCursor cursor; + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators() || !internalIndex.hasRangeQuerySupport()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + cursor = index.iterateEntriesMajor(key, false, ascSortOrder); + } else { + // if we have situation like "field1 = 1 AND field2 > 2" + // then we fetch collection which left not included boundary is the smallest composite key in the + // index that contains keys with values field1=1 and field2=2 and which right included boundary + // is the biggest composite key in the index that contains key with value field1=1. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams.subList(0, keyParams.size() - 1)); + + if (keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, false, keyTwo, true, ascSortOrder); + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) + if (iRight instanceof ORID) + return new ORecordId(((ORID) iRight).next()); + else { + if (iRight instanceof OSQLFilterItemParameter + && ((OSQLFilterItemParameter) iRight).getValue(null, null, null) instanceof ORID) + return new ORecordId(((ORID) ((OSQLFilterItemParameter) iRight).getValue(null, null, null)).next()); + } + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().compare(iFirstField, iSecondField) > 0; + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajorEquals.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajorEquals.java new file mode 100755 index 00000000000..e757a0482ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMajorEquals.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; + +import java.util.List; + +/** + * MAJOR EQUALS operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMajorEquals extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate=false; + + public OQueryOperatorMajorEquals() { + super(">=", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final Object right = OType.convert(iRight, iLeft.getClass()); + if (right == null) + return false; + return ((Comparable) iLeft).compareTo(right) >= 0; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iRight == null || iLeft == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + OIndexCursor cursor; + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators() || !internalIndex.hasRangeQuerySupport()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + cursor = index.iterateEntriesMajor(key, true, ascSortOrder); + } else { + // if we have situation like "field1 = 1 AND field2 >= 2" + // then we fetch collection which left included boundary is the smallest composite key in the + // index that contains keys with values field1=1 and field2=2 and which right included boundary + // is the biggest composite key in the index that contains key with value field1=1. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams.subList(0, keyParams.size() - 1)); + + if (keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) + if (iRight instanceof ORID) + return (ORID) iRight; + else { + if (iRight instanceof OSQLFilterItemParameter + && ((OSQLFilterItemParameter) iRight).getValue(null, null, null) instanceof ORID) + return (ORID) ((OSQLFilterItemParameter) iRight).getValue(null, null, null); + } + + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().compare(iFirstField, iSecondField) >= 0; + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMatches.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMatches.java new file mode 100644 index 00000000000..cf76adff0b4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMatches.java @@ -0,0 +1,71 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +import java.util.regex.Pattern; + +/** + * MATCHES operator. Matches the left value against the regular expression contained in the second one. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMatches extends OQueryOperatorEqualityNotNulls { + + public OQueryOperatorMatches() { + super("MATCHES", 5, false); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + return this.matches(iLeft.toString(), (String) iRight, iContext); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + return null; + } + + private boolean matches(final String iValue, final String iRegex, final OCommandContext iContext) { + final String key = "MATCHES_" + iRegex.hashCode(); + Pattern p = (Pattern) iContext.getVariable(key); + if (p == null) { + p = Pattern.compile(iRegex); + iContext.setVariable(key, p); + } + return p.matcher(iValue).matches(); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinor.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinor.java new file mode 100755 index 00000000000..e06efa53db6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinor.java @@ -0,0 +1,150 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; + +import java.util.List; + +/** + * MINOR operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMinor extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate=false; + + public OQueryOperatorMinor() { + super("<", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, OCommandContext iContext) { + final Object right = OType.convert(iRight, iLeft.getClass()); + if (right == null) + return false; + return ((Comparable) iLeft).compareTo(right) < 0; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iRight == null || iLeft == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + if (!internalIndex.canBeUsedInEqualityOperators() || !internalIndex.hasRangeQuerySupport()) + return null; + + final OIndexCursor cursor; + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + cursor = index.iterateEntriesMinor(key, false, ascSortOrder); + } else { + // if we have situation like "field1 = 1 AND field2 < 2" + // then we fetch collection which left included boundary is the smallest composite key in the + // index that contains key with value field1=1 and which right not included boundary + // is the biggest composite key in the index that contains key with values field1=1 and field2=2. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams.subList(0, keyParams.size() - 1)); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, false, ascSortOrder); + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) + if (iRight instanceof ORID) + return (ORID) iRight; + else { + if (iRight instanceof OSQLFilterItemParameter + && ((OSQLFilterItemParameter) iRight).getValue(null, null, null) instanceof ORID) + return (ORID) ((OSQLFilterItemParameter) iRight).getValue(null, null, null); + } + + return null; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().compare(iFirstField, iSecondField) < 0; + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinorEquals.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinorEquals.java new file mode 100755 index 00000000000..b10a6db21ec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorMinorEquals.java @@ -0,0 +1,147 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.index.OCompositeIndexDefinition; +import com.orientechnologies.orient.core.index.OIndex; +import com.orientechnologies.orient.core.index.OIndexCursor; +import com.orientechnologies.orient.core.index.OIndexDefinition; +import com.orientechnologies.orient.core.index.OIndexDefinitionMultiValue; +import com.orientechnologies.orient.core.index.OIndexInternal; +import com.orientechnologies.orient.core.metadata.schema.OType; +import com.orientechnologies.orient.core.record.impl.ODocumentHelper; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemParameter; + +import java.util.List; + +/** + * MINOR EQUALS operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorMinorEquals extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate = true; + + public OQueryOperatorMinorEquals() { + super("<=", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + @SuppressWarnings("unchecked") + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight, OCommandContext iContext) { + final Object right = OType.convert(iRight, iLeft.getClass()); + if (right == null) + return false; + return ((Comparable) iLeft).compareTo(right) <= 0; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iRight == null || iLeft == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_METHOD; + } + + @Override + public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex index, List keyParams, boolean ascSortOrder) { + final OIndexDefinition indexDefinition = index.getDefinition(); + + final OIndexInternal internalIndex = index.getInternal(); + OIndexCursor cursor; + if (!internalIndex.canBeUsedInEqualityOperators() || !internalIndex.hasRangeQuerySupport()) + return null; + + if (indexDefinition.getParamCount() == 1) { + final Object key; + if (indexDefinition instanceof OIndexDefinitionMultiValue) + key = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(keyParams.get(0)); + else + key = indexDefinition.createValue(keyParams); + + if (key == null) + return null; + + cursor = index.iterateEntriesMinor(key, true, ascSortOrder); + } else { + // if we have situation like "field1 = 1 AND field2 <= 2" + // then we fetch collection which left included boundary is the smallest composite key in the + // index that contains key with value field1=1 and which right not included boundary + // is the biggest composite key in the index that contains key with value field1=1 and field2=2. + + final OCompositeIndexDefinition compositeIndexDefinition = (OCompositeIndexDefinition) indexDefinition; + + final Object keyOne = compositeIndexDefinition.createSingleValue(keyParams.subList(0, keyParams.size() - 1)); + + if (keyOne == null) + return null; + + final Object keyTwo = compositeIndexDefinition.createSingleValue(keyParams); + + if (keyTwo == null) + return null; + + cursor = index.iterateEntriesBetween(keyOne, true, keyTwo, true, ascSortOrder); + } + + updateProfiler(iContext, index, keyParams, indexDefinition); + return cursor; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + if (iLeft instanceof OSQLFilterItemField && ODocumentHelper.ATTRIBUTE_RID.equals(((OSQLFilterItemField) iLeft).getRoot())) + if (iRight instanceof ORID) + return (ORID) iRight; + else { + if (iRight instanceof OSQLFilterItemParameter && ((OSQLFilterItemParameter) iRight).getValue(null, null, null) instanceof ORID) + return (ORID) ((OSQLFilterItemParameter) iRight).getValue(null, null, null); + } + + return null; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().compare(iFirstField, iSecondField) <= 0; + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNot.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNot.java new file mode 100644 index 00000000000..91fea2872eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNot.java @@ -0,0 +1,104 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * NOT operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorNot extends OQueryOperator { + private OQueryOperator next; + + public OQueryOperatorNot() { + super("NOT", 10, true); + next = null; + } + + public OQueryOperatorNot(final OQueryOperator iNext) { + this(); + next = iNext; + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + if (next != null) + return !(Boolean) next.evaluateRecord(iRecord, null, iCondition, iLeft, iRight, iContext); + + if (iLeft == null) + return false; + return !(Boolean) iLeft; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + if (iLeft instanceof OSQLFilterCondition) { + final ORID beginRange = ((OSQLFilterCondition) iLeft).getBeginRidRange(); + final ORID endRange = ((OSQLFilterCondition) iLeft).getEndRidRange(); + + if (beginRange == null && endRange == null) + return null; + else if (beginRange == null) + return endRange; + else if (endRange == null) + return null; + else + return null; + } + + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + if (iLeft instanceof OSQLFilterCondition) { + final ORID beginRange = ((OSQLFilterCondition) iLeft).getBeginRidRange(); + final ORID endRange = ((OSQLFilterCondition) iLeft).getEndRidRange(); + + if (beginRange == null && endRange == null) + return null; + else if (beginRange == null) + return null; + else if (endRange == null) + return beginRange; + else + return null; + } + + return null; + } + + public OQueryOperator getNext() { + return next; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals.java new file mode 100644 index 00000000000..cad6f9e982a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * NOT EQUALS operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorNotEquals extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate = false; + + public OQueryOperatorNotEquals() { + super("<>", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight, OCommandContext iContext) { + return !OQueryOperatorEquals.equals(iLeft, iRight); + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return !ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isEqual(iFirstField, iSecondField); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals2.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals2.java new file mode 100644 index 00000000000..cf71481f989 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorNotEquals2.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField; +import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * NOT EQUALS operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorNotEquals2 extends OQueryOperatorEqualityNotNulls { + + private boolean binaryEvaluate = false; + + public OQueryOperatorNotEquals2() { + super("!=", 5, false); + ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined(); + if (db != null) + binaryEvaluate = db.getSerializer().getSupportBinaryEvaluate(); + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight, OCommandContext iContext) { + return !OQueryOperatorEquals.equals(iLeft, iRight); + } + + @Override + public boolean isSupportingBinaryEvaluate() { + return binaryEvaluate; + } + + @Override + public boolean evaluate(final OBinaryField iFirstField, final OBinaryField iSecondField, OCommandContext iContext) { + return !ORecordSerializerBinary.INSTANCE.getCurrentSerializer().getComparator().isEqual(iFirstField, iSecondField); + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorOr.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorOr.java new file mode 100644 index 00000000000..0c1fb242de8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorOr.java @@ -0,0 +1,106 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * OR operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorOr extends OQueryOperator { + + public OQueryOperatorOr() { + super("OR", 3, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + if (iLeft == null) + return false; + return (Boolean) iLeft || (Boolean) iRight; + + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + if (iLeft == null || iRight == null) + return OIndexReuseType.NO_INDEX; + return OIndexReuseType.INDEX_UNION; + } + + @Override + public ORID getBeginRidRange(final Object iLeft, final Object iRight) { + final ORID leftRange; + final ORID rightRange; + + if (iLeft instanceof OSQLFilterCondition) + leftRange = ((OSQLFilterCondition) iLeft).getBeginRidRange(); + else + leftRange = null; + + if (iRight instanceof OSQLFilterCondition) + rightRange = ((OSQLFilterCondition) iRight).getBeginRidRange(); + else + rightRange = null; + + if (leftRange == null || rightRange == null) + return null; + else + return leftRange.compareTo(rightRange) <= 0 ? leftRange : rightRange; + } + + @Override + public ORID getEndRidRange(final Object iLeft, final Object iRight) { + final ORID leftRange; + final ORID rightRange; + + if (iLeft instanceof OSQLFilterCondition) + leftRange = ((OSQLFilterCondition) iLeft).getEndRidRange(); + else + leftRange = null; + + if (iRight instanceof OSQLFilterCondition) + rightRange = ((OSQLFilterCondition) iRight).getEndRidRange(); + else + rightRange = null; + + if (leftRange == null || rightRange == null) + return null; + else + return leftRange.compareTo(rightRange) >= 0 ? leftRange : rightRange; + } + + @Override + public boolean canShortCircuit(Object l) { + if (Boolean.TRUE.equals(l)) { + return true; + } + return false; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorTraverse.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorTraverse.java new file mode 100755 index 00000000000..96f38b0f821 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryOperatorTraverse.java @@ -0,0 +1,217 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.db.record.ORecordElement; +import com.orientechnologies.orient.core.exception.ORecordNotFoundException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.query.OQueryRuntimeValueMulti; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemFieldAny; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * TRAVERSE operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorTraverse extends OQueryOperatorEqualityNotNulls { + private int startDeepLevel = 0; // FIRST + private int endDeepLevel = -1; // INFINITE + private String[] cfgFields; + + public OQueryOperatorTraverse() { + super("TRAVERSE", 5, false, 1, true); + } + + public OQueryOperatorTraverse(final int startDeepLevel, final int endDeepLevel, final String[] iFieldList) { + this(); + this.startDeepLevel = startDeepLevel; + this.endDeepLevel = endDeepLevel; + this.cfgFields = iFieldList; + } + + @Override + public String getSyntax() { + return " TRAVERSE[( [, [,]] )] ( )"; + } + + @Override + protected boolean evaluateExpression(final OIdentifiable iRecord, final OSQLFilterCondition iCondition, final Object iLeft, + final Object iRight, final OCommandContext iContext) { + final OSQLFilterCondition condition; + final Object target; + + if (iCondition.getLeft() instanceof OSQLFilterCondition) { + condition = (OSQLFilterCondition) iCondition.getLeft(); + target = iRight; + } else { + condition = (OSQLFilterCondition) iCondition.getRight(); + target = iLeft; + } + + final Set evaluatedRecords = new HashSet(); + return traverse(target, condition, 0, evaluatedRecords, iContext); + } + + @SuppressWarnings("unchecked") + private boolean traverse(Object iTarget, final OSQLFilterCondition iCondition, final int iLevel, + final Set iEvaluatedRecords, final OCommandContext iContext) { + if (endDeepLevel > -1 && iLevel > endDeepLevel) + return false; + + if (iTarget instanceof OIdentifiable) { + if (iEvaluatedRecords.contains(((OIdentifiable) iTarget).getIdentity())) + // ALREADY EVALUATED + return false; + + // TRANSFORM THE ORID IN ODOCUMENT + iTarget = ((OIdentifiable) iTarget).getRecord(); + } + + if (iTarget instanceof ODocument) { + final ODocument target = (ODocument) iTarget; + + iEvaluatedRecords.add(target.getIdentity()); + + if (target.getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) + try { + target.load(); + } catch (final ORecordNotFoundException e) { + // INVALID RID + return false; + } + + if (iLevel >= startDeepLevel && (Boolean) iCondition.evaluate(target, null, iContext) == Boolean.TRUE) + return true; + + // TRAVERSE THE DOCUMENT ITSELF + if (cfgFields != null) + for (final String cfgField : cfgFields) { + if (cfgField.equalsIgnoreCase(OSQLFilterItemFieldAny.FULL_NAME)) { + // ANY + for (final String fieldName : target.fieldNames()) + if (traverse(target.rawField(fieldName), iCondition, iLevel + 1, iEvaluatedRecords, iContext)) + return true; + } else if (cfgField.equalsIgnoreCase(OSQLFilterItemFieldAny.FULL_NAME)) { + // ALL + for (final String fieldName : target.fieldNames()) + if (!traverse(target.rawField(fieldName), iCondition, iLevel + 1, iEvaluatedRecords, iContext)) + return false; + return true; + } else { + if (traverse(target.rawField(cfgField), iCondition, iLevel + 1, iEvaluatedRecords, iContext)) + return true; + } + } + + } else if (iTarget instanceof OQueryRuntimeValueMulti) { + + final OQueryRuntimeValueMulti multi = (OQueryRuntimeValueMulti) iTarget; + for (final Object o : multi.getValues()) { + if (traverse(o, iCondition, iLevel + 1, iEvaluatedRecords, iContext) == Boolean.TRUE) + return true; + } + } else if (iTarget instanceof Map) { + + final Map map = (Map) iTarget; + for (final Object o : map.values()) { + if (traverse(o, iCondition, iLevel + 1, iEvaluatedRecords, iContext) == Boolean.TRUE) + return true; + } + } else if (OMultiValue.isMultiValue(iTarget)) { + final Iterable collection = OMultiValue.getMultiValueIterable(iTarget, false); + for (final Object o : collection) { + if (traverse(o, iCondition, iLevel + 1, iEvaluatedRecords, iContext) == Boolean.TRUE) + return true; + } + } else if (iTarget instanceof Iterator) { + final Iterator iterator = (Iterator) iTarget; + while (iterator.hasNext()) { + if (traverse(iterator.next(), iCondition, iLevel + 1, iEvaluatedRecords, iContext) == Boolean.TRUE) + return true; + } + } + + return false; + } + + @Override + public OQueryOperator configure(final List iParams) { + if (iParams == null) + return this; + + final int start = !iParams.isEmpty() ? Integer.parseInt(iParams.get(0)) : startDeepLevel; + final int end = iParams.size() > 1 ? Integer.parseInt(iParams.get(1)) : endDeepLevel; + + String[] fields = new String[] { "any()" }; + if (iParams.size() > 2) { + String f = iParams.get(2); + if (f.startsWith("'") || f.startsWith("\"")) + f = f.substring(1, f.length() - 1); + fields = f.split(","); + } + + return new OQueryOperatorTraverse(start, end, fields); + } + + public int getStartDeepLevel() { + return startDeepLevel; + } + + public int getEndDeepLevel() { + return endDeepLevel; + } + + public String[] getCfgFields() { + return cfgFields; + } + + @Override + public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public String toString() { + return String.format("%s(%d,%d,%s)", keyword, startDeepLevel, endDeepLevel, Arrays.toString(cfgFields)); + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryTargetOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryTargetOperator.java new file mode 100644 index 00000000000..57e8bba3ca5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/OQueryTargetOperator.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator; + +import java.util.Collection; +import java.util.List; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabase; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; + +/** + * Operator that filters the target records. + * + * @author Luca Garulli + * + */ +public abstract class OQueryTargetOperator extends OQueryOperator { + protected OQueryTargetOperator(final String iKeyword, final int iPrecedence, final boolean iLogical) { + super(iKeyword, iPrecedence, false); + } + + public abstract Collection filterRecords(final ODatabase iRecord, final List iTargetClasses, + final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight); + + /** + * At run-time the evaluation per record must return always true since the recordset are filtered at the beginning unless an + * operator can work in both modes. In this case sub-class must extend it. + */ + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + final Object iLeft, final Object iRight, OCommandContext iContext) { + return true; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorDivide.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorDivide.java new file mode 100644 index 00000000000..ea5a654205f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorDivide.java @@ -0,0 +1,92 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * DIVIDE "/" operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorDivide extends OQueryOperator { + + public OQueryOperatorDivide() { + super("/", 10, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + Object iLeft, Object iRight, OCommandContext iContext) { + if (iRight == null || iLeft == null) + return null; + + if (iLeft instanceof Date) + iLeft = ((Date) iLeft).getTime(); + if (iRight instanceof Date) + iRight = ((Date) iRight).getTime(); + + if (iLeft instanceof Number && iRight instanceof Number) { + final Number l = (Number) iLeft; + final Number r = (Number) iRight; + Class maxPrecisionClass = OQueryOperatorMultiply.getMaxPrecisionClass(l, r); + if (Integer.class.equals(maxPrecisionClass)) + return l.intValue() / r.intValue(); + else if (Long.class.equals(maxPrecisionClass)) + return l.longValue() / r.longValue(); + else if (Short.class.equals(maxPrecisionClass)) + return l.shortValue() / r.shortValue(); + else if (Float.class.equals(maxPrecisionClass)) + return l.floatValue() / r.floatValue(); + else if (Double.class.equals(maxPrecisionClass)) + return l.doubleValue() / r.doubleValue(); + else if (BigDecimal.class.equals(maxPrecisionClass)) { + return (OQueryOperatorMultiply.toBigDecimal(l)).divide(OQueryOperatorMultiply.toBigDecimal(r)); + } + } + + return null; + } + + @Override + public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMinus.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMinus.java new file mode 100644 index 00000000000..e5b95f2e5ad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMinus.java @@ -0,0 +1,92 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * MINUS "-" operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMinus extends OQueryOperator { + + public OQueryOperatorMinus() { + super("-", 9, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + Object iLeft, Object iRight, OCommandContext iContext) { + if (iRight == null) + return iLeft; + + if (iLeft instanceof Date) + iLeft = ((Date) iLeft).getTime(); + if (iRight instanceof Date) + iRight = ((Date) iRight).getTime(); + + if (iLeft instanceof Number && iRight instanceof Number) { + final Number l = (Number) iLeft; + final Number r = (Number) iRight; + Class maxPrecisionClass = OQueryOperatorMultiply.getMaxPrecisionClass(l, r); + if (Integer.class.equals(maxPrecisionClass)) + return OQueryOperatorMultiply.tryDownscaleToInt(l.longValue() - r.longValue()); + else if (Long.class.equals(maxPrecisionClass)) + return l.longValue() - r.longValue(); + else if (Short.class.equals(maxPrecisionClass)) + return l.shortValue() - r.shortValue(); + else if (Float.class.equals(maxPrecisionClass)) + return l.floatValue() - r.floatValue(); + else if (Double.class.equals(maxPrecisionClass)) + return l.doubleValue() - r.doubleValue(); + else if (BigDecimal.class.equals(maxPrecisionClass)) { + return (OQueryOperatorMultiply.toBigDecimal(l)).subtract(OQueryOperatorMultiply.toBigDecimal(r)); + } + } + + return null; + } + + @Override + public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMod.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMod.java new file mode 100644 index 00000000000..92d6cc2877b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMod.java @@ -0,0 +1,102 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator.math; + +import java.math.BigDecimal; +import java.util.Date; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +/** + * MOD "%" operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorMod extends OQueryOperator { + + public OQueryOperatorMod() { + super("%", 10, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + Object iLeft, Object iRight, OCommandContext iContext) { + if (iRight == null || iLeft == null) + return null; + + if (iLeft instanceof Date) + iLeft = ((Date) iLeft).getTime(); + if (iRight instanceof Date) + iRight = ((Date) iRight).getTime(); + + if (iLeft instanceof Number && iRight instanceof Number) { + final Number l = (Number) iLeft; + final Number r = (Number) iRight; + if (l instanceof Integer) + return l.intValue() % r.intValue(); + else if (l instanceof Long) + return l.longValue() % r.longValue(); + else if (l instanceof Short) + return l.shortValue() % r.shortValue(); + else if (l instanceof Float) + return l.floatValue() % r.floatValue(); + else if (l instanceof Double) + return l.doubleValue() % r.doubleValue(); + else if (l instanceof BigDecimal) { + if (r instanceof BigDecimal) + return ((BigDecimal) l).remainder((BigDecimal) r); + else if (r instanceof Float) + return ((BigDecimal) l).remainder(new BigDecimal(r.floatValue())); + else if (r instanceof Double) + return ((BigDecimal) l).remainder(new BigDecimal(r.doubleValue())); + else if (r instanceof Long) + return ((BigDecimal) l).remainder(new BigDecimal(r.longValue())); + else if (r instanceof Integer) + return ((BigDecimal) l).remainder(new BigDecimal(r.intValue())); + else if (r instanceof Short) + return ((BigDecimal) l).remainder(new BigDecimal(r.shortValue())); + } + } + + return null; + } + + @Override + public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMultiply.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMultiply.java new file mode 100644 index 00000000000..15a3a14d99f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorMultiply.java @@ -0,0 +1,146 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * MULTIPLY "*" operator. + * + * @author Luca Garulli + */ +public class OQueryOperatorMultiply extends OQueryOperator { + + public OQueryOperatorMultiply() { + super("*", 10, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + Object iLeft, Object iRight, OCommandContext iContext) { + if (iRight == null || iLeft == null) + return null; + + if (iLeft instanceof Date) + iLeft = ((Date) iLeft).getTime(); + if (iRight instanceof Date) + iRight = ((Date) iRight).getTime(); + + if (iLeft instanceof Number && iRight instanceof Number) { + final Number l = (Number) iLeft; + final Number r = (Number) iRight; + Class maxPrecisionClass = getMaxPrecisionClass(l, r); + if (Integer.class.equals(maxPrecisionClass)) + return tryDownscaleToInt(l.longValue() * r.longValue()); + else if (Long.class.equals(maxPrecisionClass)) + return l.longValue() * r.longValue(); + else if (Short.class.equals(maxPrecisionClass)) + return l.shortValue() * r.shortValue(); + else if (Float.class.equals(maxPrecisionClass)) + return l.floatValue() * r.floatValue(); + else if (Double.class.equals(maxPrecisionClass)) + return l.doubleValue() * r.doubleValue(); + else if (BigDecimal.class.equals(maxPrecisionClass)) { + return (toBigDecimal(l)).multiply(toBigDecimal(r)); + } + } + + return null; + } + + public static BigDecimal toBigDecimal(Number number) { + if (number instanceof BigDecimal) { + return (BigDecimal) number; + } + if (number instanceof Double) { + return new BigDecimal(number.doubleValue()); + } + if (number instanceof Float) { + return new BigDecimal(number.floatValue()); + } + if (number instanceof Long) { + return new BigDecimal(number.longValue()); + } + if (number instanceof Integer) { + return new BigDecimal(number.intValue()); + } + if (number instanceof Short) { + return new BigDecimal(number.intValue()); + } + + return null; + } + + public static Class getMaxPrecisionClass(Number l, Number r) { + Class lClass = l.getClass(); + Class rClass = r.getClass(); + if (lClass.equals(BigDecimal.class) || rClass.equals(BigDecimal.class)) { + return BigDecimal.class; + } + if (lClass.equals(Double.class) || rClass.equals(Double.class)) { + return Double.class; + } + if (lClass.equals(Float.class) || rClass.equals(Float.class)) { + return Float.class; + } + if (lClass.equals(Long.class) || rClass.equals(Long.class)) { + return Long.class; + } + if (lClass.equals(Integer.class) || rClass.equals(Integer.class)) { + return Integer.class; + } + if (lClass.equals(Short.class) || rClass.equals(Short.class)) { + return Short.class; + } + + return null; + } + + public static Object tryDownscaleToInt(long value) { + if (value < Integer.MAX_VALUE && value > Integer.MIN_VALUE) { + return (int) value; + } + return value; + } + + @Override + public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorPlus.java b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorPlus.java new file mode 100644 index 00000000000..f704baf39fc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/operator/math/OQueryOperatorPlus.java @@ -0,0 +1,98 @@ +/* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ +package com.orientechnologies.orient.core.sql.operator.math; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition; +import com.orientechnologies.orient.core.sql.operator.OIndexReuseType; +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * PLUS "+" operator. + * + * @author Luca Garulli + * + */ +public class OQueryOperatorPlus extends OQueryOperator { + + public OQueryOperatorPlus() { + super("+", 9, false); + } + + @Override + public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition, + Object iLeft, Object iRight, OCommandContext iContext) { + if (iRight == null) + return iLeft; + if (iLeft == null) + return iRight; + + if (iLeft instanceof Date) + iLeft = ((Date) iLeft).getTime(); + if (iRight instanceof Date) + iRight = ((Date) iRight).getTime(); + + if (iLeft instanceof String) + return (String) iLeft + iRight.toString(); + else if (iRight instanceof String) + return iLeft.toString() + (String) iRight; + else if (iLeft instanceof Number && iRight instanceof Number) { + final Number l = (Number) iLeft; + final Number r = (Number) iRight; + Class maxPrecisionClass = OQueryOperatorMultiply.getMaxPrecisionClass(l, r); + if (Integer.class.equals(maxPrecisionClass)) + return OQueryOperatorMultiply.tryDownscaleToInt(l.longValue() + r.longValue()); + else if (Long.class.equals(maxPrecisionClass)) + return l.longValue() + r.longValue(); + else if (Short.class.equals(maxPrecisionClass)) + return l.shortValue() + r.shortValue(); + else if (Float.class.equals(maxPrecisionClass)) + return l.floatValue() + r.floatValue(); + else if (Double.class.equals(maxPrecisionClass)) + return l.doubleValue() + r.doubleValue(); + else if (BigDecimal.class.equals(maxPrecisionClass)) { + return (OQueryOperatorMultiply.toBigDecimal(l)).add(OQueryOperatorMultiply.toBigDecimal(r)); + } + } + + return null; + } + + @Override + public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) { + return OIndexReuseType.NO_INDEX; + } + + @Override + public ORID getBeginRidRange(Object iLeft, Object iRight) { + return null; + } + + @Override + public ORID getEndRidRange(Object iLeft, Object iRight) { + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/CharStream.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/CharStream.java new file mode 100644 index 00000000000..7ffa5cbe0ac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/CharStream.java @@ -0,0 +1,115 @@ +/* Generated By:JavaCC: Do not edit this line. CharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +/** + * This interface describes a character stream that maintains line and + * column number positions of the characters. It also has the capability + * to backup the stream to some extent. An implementation of this + * interface is used in the TokenManager implementation generated by + * JavaCCParser. + * + * All the methods except backup can be implemented in any fashion. backup + * needs to be implemented correctly for the correct operation of the lexer. + * Rest of the methods are all used to get information like line number, + * column number and the String that constitutes a token and are not used + * by the lexer. Hence their implementation won't affect the generated lexer's + * operation. + */ + +public +interface CharStream { + + /** + * Returns the next character from the selected input. The method + * of selecting the input is the responsibility of the class + * implementing this interface. Can throw any java.io.IOException. + */ + char readChar() throws java.io.IOException; + + @Deprecated + /** + * Returns the column position of the character last read. + * @deprecated + * @see #getEndColumn + */ + int getColumn(); + + @Deprecated + /** + * Returns the line number of the character last read. + * @deprecated + * @see #getEndLine + */ + int getLine(); + + /** + * Returns the column number of the last character for current token (being + * matched after the last call to BeginTOken). + */ + int getEndColumn(); + + /** + * Returns the line number of the last character for current token (being + * matched after the last call to BeginTOken). + */ + int getEndLine(); + + /** + * Returns the column number of the first character for current token (being + * matched after the last call to BeginTOken). + */ + int getBeginColumn(); + + /** + * Returns the line number of the first character for current token (being + * matched after the last call to BeginTOken). + */ + int getBeginLine(); + + /** + * Backs up the input stream by amount steps. Lexer calls this method if it + * had already read some characters, but could not use them to match a + * (longer) token. So, they will be used again as the prefix of the next + * token and it is the implemetation's responsibility to do this right. + */ + void backup(int amount); + + /** + * Returns the next character that marks the beginning of the next token. + * All characters must remain in the buffer between two successive calls + * to this method to implement backup correctly. + */ + char BeginToken() throws java.io.IOException; + + /** + * Returns a string made up of characters from the marked token beginning + * to the current buffer position. Implementations have the choice of returning + * anything that they want to. For example, for efficiency, one might decide + * to just return null, which is a valid implementation. + */ + String GetImage(); + + /** + * Returns an array of characters that make up the suffix of length 'len' for + * the currently matched token. This is used to build up the matched string + * for use in actions in the case of MORE. A simple and inefficient + * implementation of this is as follows : + * + * { + * String t = GetImage(); + * return t.substring(t.length() - len, t.length()).toCharArray(); + * } + */ + char[] GetSuffix(int len); + + /** + * The lexer calls this function to indicate that it is done with the stream + * and hence implementations can free any resources held by this class. + * Again, the body of this function can be just empty and it will not + * affect the lexer's operation. + */ + void Done(); + +} +/* JavaCC - OriginalChecksum=8356b3d537c263ca36ca197ba64d7402 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JJTOrientSqlState.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JJTOrientSqlState.java new file mode 100644 index 00000000000..d50bcd76818 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JJTOrientSqlState.java @@ -0,0 +1,123 @@ +/* Generated By:JavaCC: Do not edit this line. JJTOrientSqlState.java Version 5.0 */ +package com.orientechnologies.orient.core.sql.parser; + +public class JJTOrientSqlState { + private java.util.List nodes; + private java.util.List marks; + + private int sp; // number of nodes on stack + private int mk; // current mark + private boolean node_created; + + public JJTOrientSqlState() { + nodes = new java.util.ArrayList(); + marks = new java.util.ArrayList(); + sp = 0; + mk = 0; + } + + /* Determines whether the current node was actually closed and + pushed. This should only be called in the final user action of a + node scope. */ + public boolean nodeCreated() { + return node_created; + } + + /* Call this to reinitialize the node stack. It is called + automatically by the parser's ReInit() method. */ + public void reset() { + nodes.clear(); + marks.clear(); + sp = 0; + mk = 0; + } + + /* Returns the root node of the AST. It only makes sense to call + this after a successful parse. */ + public Node rootNode() { + return nodes.get(0); + } + + /* Pushes a node on to the stack. */ + public void pushNode(Node n) { + nodes.add(n); + ++sp; + } + + /* Returns the node on the top of the stack, and remove it from the + stack. */ + public Node popNode() { + if (--sp < mk) { + mk = marks.remove(marks.size()-1); + } + return nodes.remove(nodes.size()-1); + } + + /* Returns the node currently on the top of the stack. */ + public Node peekNode() { + return nodes.get(nodes.size()-1); + } + + /* Returns the number of children on the stack in the current node + scope. */ + public int nodeArity() { + return sp - mk; + } + + + public void clearNodeScope(Node n) { + while (sp > mk) { + popNode(); + } + mk = marks.remove(marks.size()-1); + } + + + public void openNodeScope(Node n) { + marks.add(mk); + mk = sp; + n.jjtOpen(); + } + + + /* A definite node is constructed from a specified number of + children. That number of nodes are popped from the stack and + made the children of the definite node. Then the definite node + is pushed on to the stack. */ + public void closeNodeScope(Node n, int num) { + mk = marks.remove(marks.size()-1); + while (num-- > 0) { + Node c = popNode(); + c.jjtSetParent(n); + n.jjtAddChild(c, num); + } + n.jjtClose(); + pushNode(n); + node_created = true; + } + + + /* A conditional node is constructed if its condition is true. All + the nodes that have been pushed since the node was opened are + made children of the conditional node, which is then pushed + on to the stack. If the condition is false the node is not + constructed and they are left on the stack. */ + public void closeNodeScope(Node n, boolean condition) { + if (condition) { + int a = nodeArity(); + mk = marks.remove(marks.size()-1); + while (a-- > 0) { + Node c = popNode(); + c.jjtSetParent(n); + n.jjtAddChild(c, a); + } + n.jjtClose(); + pushNode(n); + node_created = true; + } else { + mk = marks.remove(marks.size()-1); + node_created = false; + } + } +} +/* JavaCC - OriginalChecksum=744a6fb68d2385b94125fa527a3bbd2a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JavaCharStream.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JavaCharStream.java new file mode 100755 index 00000000000..bdb01715473 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/JavaCharStream.java @@ -0,0 +1,617 @@ +/* Generated By:JavaCC: Do not edit this line. JavaCharStream.java Version 5.0 */ +/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (with java-like unicode escape processing). + */ + +public +class JavaCharStream implements CharStream +{ + /** Whether parser is static. */ + public static final boolean staticFlag = false; + + static final int hexval(char c) throws java.io.IOException { + switch(c) + { + case '0' : + return 0; + case '1' : + return 1; + case '2' : + return 2; + case '3' : + return 3; + case '4' : + return 4; + case '5' : + return 5; + case '6' : + return 6; + case '7' : + return 7; + case '8' : + return 8; + case '9' : + return 9; + + case 'a' : + case 'A' : + return 10; + case 'b' : + case 'B' : + return 11; + case 'c' : + case 'C' : + return 12; + case 'd' : + case 'D' : + return 13; + case 'e' : + case 'E' : + return 14; + case 'f' : + case 'F' : + return 15; + } + + throw new java.io.IOException(); // Should never come here + } + +/** Position in buffer. */ + public int bufpos = -1; + int bufsize; + int available; + int tokenBegin; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] nextCharBuf; + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int nextCharInd = -1; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + bufpos += (bufsize - tokenBegin); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + bufpos -= tokenBegin; + } + } + catch (Throwable t) + { + throw new Error(t.getMessage(), t); + } + + available = (bufsize += 2048); + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + int i; + if (maxNextCharInd == 4096) + maxNextCharInd = nextCharInd = 0; + + try { + if ((i = inputStream.read(nextCharBuf, maxNextCharInd, + 4096 - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + if (bufpos != 0) + { + --bufpos; + backup(0); + } + else + { + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + throw e; + } + } + + protected char ReadByte() throws java.io.IOException + { + if (++nextCharInd >= maxNextCharInd) + FillBuff(); + + return nextCharBuf[nextCharInd]; + } + +/** @return starting character for token. */ + public char BeginToken() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + tokenBegin = bufpos; + return buffer[bufpos]; + } + + tokenBegin = 0; + bufpos = -1; + + return readChar(); + } + + protected void AdjustBuffSize() + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = 0; + available = tokenBegin; + } + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + +/** Read a character. */ + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + char c; + + if (++bufpos == available) + AdjustBuffSize(); + + if ((buffer[bufpos] = c = ReadByte()) == '\\') + { + UpdateLineColumn(c); + + int backSlashCnt = 1; + + for (;;) // Read all the backslashes + { + if (++bufpos == available) + AdjustBuffSize(); + + try + { + if ((buffer[bufpos] = c = ReadByte()) != '\\') + { + UpdateLineColumn(c); + // found a non-backslash char. + if ((c == 'u') && ((backSlashCnt & 1) == 1)) + { + if (--bufpos < 0) + bufpos = bufsize - 1; + + break; + } + + backup(backSlashCnt); + return '\\'; + } + } + catch(java.io.IOException e) + { + // We are returning one backslash so we should only backup (count-1) + if (backSlashCnt > 1) + backup(backSlashCnt-1); + + return '\\'; + } + + UpdateLineColumn(c); + backSlashCnt++; + } + + // Here, we have seen an odd number of backslash's followed by a 'u' + try + { + while ((c = ReadByte()) == 'u') + ++column; + + buffer[bufpos] = c = (char)(hexval(c) << 12 | + hexval(ReadByte()) << 8 | + hexval(ReadByte()) << 4 | + hexval(ReadByte())); + + column += 4; + } + catch(java.io.IOException e) + { + throw new Error("Invalid escape character at line " + line + + " column " + column, e); + } + + if (backSlashCnt == 1) + return c; + else + { + backup(backSlashCnt - 1); + return '\\'; + } + } + else + { + UpdateLineColumn(c); + return c; + } + } + + @Deprecated + /** + * @deprecated + * @see #getEndColumn + */ + public int getColumn() { + return bufcolumn[bufpos]; + } + + @Deprecated + /** + * @deprecated + * @see #getEndLine + */ + public int getLine() { + return bufline[bufpos]; + } + +/** Get end column. */ + public int getEndColumn() { + return bufcolumn[bufpos]; + } + +/** Get end line. */ + public int getEndLine() { + return bufline[bufpos]; + } + +/** @return column of token start */ + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + +/** @return line number of token start */ + public int getBeginLine() { + return bufline[tokenBegin]; + } + +/** Retreat. */ + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + +/** Constructor. */ + public JavaCharStream(java.io.Reader dstream, + int startline, int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + nextCharBuf = new char[4096]; + } + +/** Constructor. */ + public JavaCharStream(java.io.Reader dstream, + int startline, int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + +/** Constructor. */ + public JavaCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } +/** Reinitialise. */ + public void ReInit(java.io.Reader dstream, + int startline, int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + nextCharBuf = new char[4096]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + nextCharInd = bufpos = -1; + } + +/** Reinitialise. */ + public void ReInit(java.io.Reader dstream, + int startline, int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + +/** Reinitialise. */ + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096); + } + +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + +/** Constructor. */ + public JavaCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + +/** Reinitialise. */ + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + + /** @return token image as String */ + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + /** @return suffix */ + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** Set buffers back to null when finished. */ + public void Done() + { + nextCharBuf = null; + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} +/* JavaCC - OriginalChecksum=846f18867a6b03b776ece99d4ba5c6d8 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Node.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Node.java new file mode 100644 index 00000000000..eed491ef609 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Node.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. Node.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +/* All AST nodes must implement this interface. It provides basic + machinery for constructing the parent and child relationships + between nodes. */ + +public +interface Node { + + /** This method is called after the node has been made the current + node. It indicates that child nodes can now be added to it. */ + public void jjtOpen(); + + /** This method is called after all the child nodes have been + added. */ + public void jjtClose(); + + /** This pair of methods are used to inform the node of its + parent. */ + public void jjtSetParent(Node n); + public Node jjtGetParent(); + + /** This method tells the node to add its argument to the node's + list of children. */ + public void jjtAddChild(Node n, int i); + + /** This method returns a child node. The children are numbered + from zero, left to right. */ + public Node jjtGetChild(int i); + + /** Return the number of children the node has. */ + public int jjtGetNumChildren(); + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data); +} +/* JavaCC - OriginalChecksum=8a51f6ec86184506d7baca4d2245af96 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlias.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlias.java new file mode 100644 index 00000000000..d2f7cf7f1c8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlias.java @@ -0,0 +1,27 @@ +/* Generated By:JJTree: Do not edit this line. OAlias.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OAlias extends SimpleNode { + public OAlias(int id) { + super(id); + } + + public OAlias(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + + } +} +/* JavaCC - OriginalChecksum=c0c2ff315abe152a8ea5f2ecafd0f853 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClassStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClassStatement.java new file mode 100644 index 00000000000..a5a71387d68 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClassStatement.java @@ -0,0 +1,130 @@ +/* Generated By:JJTree: Do not edit this line. OAlterClassStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.List; +import java.util.Map; + +public class OAlterClassStatement extends OStatement { + + /** + * the name of the class + */ + protected OIdentifier name; + /** + * the class property to be altered + */ + public OClass.ATTRIBUTES property; + + protected OIdentifier identifierValue; + protected List identifierListValue; + protected Boolean add; + protected Boolean remove; + protected ONumber numberValue; + public OExpression expression; + + public OIdentifier customKey; + public OExpression customValue; + + // only to manage 'round-robin' as a cluster selection strategy (not a valid identifier) + protected String customString; + + protected boolean unsafe; + + public OAlterClassStatement(int id) { + super(id); + } + + public OAlterClassStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("ALTER CLASS "); + name.toString(params, builder); + builder.append(" " + property.name() + " "); + switch (property) { + case NAME: + case SHORTNAME: + case ADDCLUSTER: + case REMOVECLUSTER: + case ENCRYPTION: + if (numberValue != null) { + numberValue.toString(params, builder);//clusters only + } else if (identifierValue != null) { + identifierValue.toString(params, builder); + } else { + builder.append("null"); + } + break; + case DESCRIPTION: + if (expression != null) { + expression.toString(params, builder); + } else { + builder.append("null"); + } + break; + case CLUSTERSELECTION: + if (identifierValue != null) { + identifierValue.toString(params, builder); + } else if (customString != null) { + builder.append('\'').append(customString).append('\''); + } else { + builder.append("null"); + } + break; + case SUPERCLASS: + if (Boolean.TRUE.equals(add)) { + builder.append("+"); + } else if (Boolean.TRUE.equals(remove)) { + builder.append("-"); + } + if (identifierValue == null) { + builder.append("null"); + } else { + identifierValue.toString(params, builder); + } + break; + case SUPERCLASSES: + if (identifierListValue == null) { + builder.append("null"); + } else { + boolean first = true; + for (OIdentifier ident : identifierListValue) { + if (!first) { + builder.append(", "); + } + ident.toString(params, builder); + first = false; + } + } + break; + case OVERSIZE: + numberValue.toString(params, builder); + break; + case STRICTMODE: + case ABSTRACT: + expression.toString(params, builder); + break; + case CUSTOM: + customKey.toString(params, builder); + if (customKey.getStringValue().equalsIgnoreCase("clear") && customValue == null) { + //do nothing + } else { + builder.append("="); + if (customValue == null) { + builder.append("null"); + } else { + customValue.toString(params, builder); + } + } + break; + } + if (unsafe) { + builder.append(" UNSAFE"); + } + } +} +/* JavaCC - OriginalChecksum=4668bb1cd336844052df941f39bdb634 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClusterStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClusterStatement.java new file mode 100644 index 00000000000..f9b5c2c0ba4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterClusterStatement.java @@ -0,0 +1,34 @@ +/* Generated By:JJTree: Do not edit this line. OAlterClusterStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OAlterClusterStatement extends OStatement { + + protected OIdentifier name; + protected OIdentifier attributeName; + protected boolean starred = false; + protected OExpression attributeValue; + + public OAlterClusterStatement(int id) { + super(id); + } + + public OAlterClusterStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("ALTER CLUSTER "); + name.toString(params, builder); + if(starred){ + builder.append("*"); + } + builder.append(" "); + attributeName.toString(params, builder); + builder.append(" "); + attributeValue.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=ed78ea0f1a05b0963db625ed1f338bd6 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterDatabaseStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterDatabaseStatement.java new file mode 100644 index 00000000000..2d003ca5a55 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterDatabaseStatement.java @@ -0,0 +1,41 @@ +/* Generated By:JJTree: Do not edit this line. OAlterDatabaseStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OAlterDatabaseStatement extends OStatement { + + OIdentifier customPropertyName; + OExpression customPropertyValue; + + OIdentifier settingName; + OExpression settingValue; + + public OAlterDatabaseStatement(int id) { + super(id); + } + + public OAlterDatabaseStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("ALTER DATABASE "); + + if (customPropertyName != null) { + builder.append("CUSTOM "); + customPropertyName.toString(params, builder); + builder.append(" = "); + customPropertyValue.toString(params, builder); + } else { + settingName.toString(params, builder); + builder.append(" "); + settingValue.toString(params, builder); + } + } + +} +/* JavaCC - OriginalChecksum=8fec57db8dd2a3b52aaa52dec7367cd4 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterPropertyStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterPropertyStatement.java new file mode 100644 index 00000000000..0c2c14ac3d1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterPropertyStatement.java @@ -0,0 +1,56 @@ +/* Generated By:JJTree: Do not edit this line. OAlterPropertyStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; + +import java.util.Map; + +public class OAlterPropertyStatement extends OStatement { + + public OIdentifier className; + + public OIdentifier propertyName; + public OIdentifier customPropertyName; + public OExpression customPropertyValue; + + public OIdentifier settingName; + public OExpression settingValue; + + public boolean clearCustom = false; + + public OAlterPropertyStatement(int id) { + super(id); + } + + public OAlterPropertyStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void validate() throws OCommandSQLParsingException { + super.validate();//TODO + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("ALTER PROPERTY "); + className.toString(params, builder); + builder.append("."); + propertyName.toString(params, builder); + if(clearCustom) { + builder.append(" CUSTOM clear"); + } else if (customPropertyName != null) { + builder.append(" CUSTOM "); + customPropertyName.toString(params, builder); + builder.append(" = "); + customPropertyValue.toString(params, builder); + } else { + builder.append(" "); + settingName.toString(params, builder); + builder.append(" "); + settingValue.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=2421f6ad3b5f1f8e18149650ff80f1e7 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterSequenceStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterSequenceStatement.java new file mode 100644 index 00000000000..032d88c1735 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAlterSequenceStatement.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OAlterSequenceStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OAlterSequenceStatement extends OStatement { + OIdentifier name; + OExpression start; + OExpression increment; + OExpression cache; + + public OAlterSequenceStatement(int id) { + super(id); + } + + public OAlterSequenceStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("ALTER SEQUENCE "); + name.toString(params, builder); + + if (start != null) { + builder.append(" START "); + start.toString(params, builder); + } + if (increment != null) { + builder.append(" INCREMENT "); + increment.toString(params, builder); + } + if (cache != null) { + builder.append(" CACHE "); + cache.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=def62b1d04db5223307fe51873a9edd0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAndBlock.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAndBlock.java new file mode 100644 index 00000000000..4b9e114f014 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OAndBlock.java @@ -0,0 +1,153 @@ +/* Generated By:JJTree: Do not edit this line. OAndBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OAndBlock extends OBooleanExpression { + List subBlocks = new ArrayList(); + + public OAndBlock(int id) { + super(id); + } + + public OAndBlock(OrientSql p, int id) { + super(p, id); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + + if (getSubBlocks() == null) { + return true; + } + + for (OBooleanExpression block : subBlocks) { + if (!block.evaluate(currentRecord, ctx)) { + return false; + } + } + return true; + + } + + public List getSubBlocks() { + return subBlocks; + } + + public void setSubBlocks(List subBlocks) { + this.subBlocks = subBlocks; + } + + public void toString(Map params, StringBuilder builder) { + if (subBlocks == null || subBlocks.size() == 0) { + return; + } + // if (subBlocks.size() == 1) { + // subBlocks.get(0).toString(params, builder); + // } + + boolean first = true; + for (OBooleanExpression expr : subBlocks) { + if (!first) { + builder.append(" AND "); + } + expr.toString(params, builder); + first = false; + } + } + + @Override protected boolean supportsBasicCalculation() { + for (OBooleanExpression expr : subBlocks) { + if (!expr.supportsBasicCalculation()) { + return false; + } + } + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + int result = 0; + for (OBooleanExpression expr : subBlocks) { + result += expr.getNumberOfExternalCalculations(); + } + return result; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + for (OBooleanExpression expr : subBlocks) { + result.addAll(expr.getExternalCalculationConditions()); + } + return result; + } + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (subBlocks == null) { + return null; + } + List result = new ArrayList(); + for (OBooleanExpression exp : subBlocks) { + List sub = exp.getIndexedFunctionConditions(iSchemaClass, database); + if (sub != null && sub.size() > 0) { + result.addAll(sub); + } + } + return result.size() == 0 ? null : result; + } + + public List flatten() { + List result = new ArrayList(); + boolean first = true; + for (OBooleanExpression sub : subBlocks) { + List subFlattened = sub.flatten(); + List oldResult = result; + result = new ArrayList(); + for (OAndBlock subAndItem : subFlattened) { + if (first) { + result.add(subAndItem); + } else { + ; + for (OAndBlock oldResultItem : oldResult) { + OAndBlock block = new OAndBlock(-1); + block.subBlocks.addAll(oldResultItem.subBlocks); + for (OBooleanExpression resultItem : subAndItem.subBlocks) { + block.subBlocks.add(resultItem); + } + result.add(block); + } + } + } + first = false; + } + return result; + } + + protected OAndBlock encapsulateInAndBlock(OBooleanExpression item) { + if (item instanceof OAndBlock) { + return (OAndBlock) item; + } + OAndBlock result = new OAndBlock(-1); + result.subBlocks.add(item); + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List result = new ArrayList(); + for (OBooleanExpression exp : subBlocks) { + List x = exp.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + } + return result.size() == 0 ? null : result; + } + +} +/* JavaCC - OriginalChecksum=cf1f66cc86cfc93d357f9fcdfa4a4604 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayNumberSelector.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayNumberSelector.java new file mode 100644 index 00000000000..8236eea2b79 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayNumberSelector.java @@ -0,0 +1,63 @@ +/* Generated By:JJTree: Do not edit this line. OArrayNumberSelector.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; + +public class OArrayNumberSelector extends SimpleNode { + private static final Object UNSET = new Object(); + private Object inputFinalValue = UNSET; + + OInputParameter inputValue; + + OMathExpression expressionValue; + + Integer integer; + + public OArrayNumberSelector(int id) { + super(id); + } + + public OArrayNumberSelector(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (inputValue != null) { + inputValue.toString(params, builder); + } else if (expressionValue != null) { + expressionValue.toString(params, builder); + } else if (integer != null) { + builder.append(integer); + } + } + + public Integer getValue(OIdentifiable iCurrentRecord, Object iResult, OCommandContext ctx) { + Object result = null; + if (inputValue != null) { + result = inputValue.bindFromInputParams(ctx.getInputParameters()); + } else if (expressionValue != null) { + result = expressionValue.execute(iCurrentRecord, ctx); + } else if (integer != null) { + result = integer; + } + + if (result == null) { + return null; + } + if (result instanceof Number) { + return ((Number) result).intValue(); + } + return null; + } + +} +/* JavaCC - OriginalChecksum=5b2e495391ede3ccdc6c25aa63c8e591 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayRangeSelector.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayRangeSelector.java new file mode 100644 index 00000000000..3b0d16843e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArrayRangeSelector.java @@ -0,0 +1,90 @@ +/* Generated By:JJTree: Do not edit this line. OArrayRangeSelector.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Arrays; +import java.util.Map; + +public class OArrayRangeSelector extends SimpleNode { + protected Integer from; + protected Integer to; + boolean newRange = false; + + protected OArrayNumberSelector fromSelector; + protected OArrayNumberSelector toSelector; + + public OArrayRangeSelector(int id) { + super(id); + } + + public OArrayRangeSelector(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (from != null) { + builder.append(from); + } else { + fromSelector.toString(params, builder); + } + if (newRange) { + builder.append("-"); + // TODO in 3.0 result.append(".."); + } else { + builder.append("-"); + } + if (to != null) { + builder.append(to); + } else { + toSelector.toString(params, builder); + } + } + + public Object execute(OIdentifiable iCurrentRecord, Object result, OCommandContext ctx) { + if (result == null) { + return null; + } + if (!OMultiValue.isMultiValue(result)) { + return null; + } + Integer lFrom = from; + if (fromSelector != null) { + lFrom = fromSelector.getValue(iCurrentRecord, result, ctx); + } + if (lFrom == null) { + lFrom = 0; + } + Integer lTo = to; + if (toSelector != null) { + lTo = toSelector.getValue(iCurrentRecord, result, ctx); + } + if (lFrom > lTo) { + return null; + } + Object[] arrayResult = OMultiValue.array(result); + + if (arrayResult == null || arrayResult.length == 0) { + return arrayResult; + } + lFrom = Math.max(lFrom, 0); + if (arrayResult.length < lFrom) { + return null; + } + lFrom = Math.min(lFrom, arrayResult.length - 1); + + lTo = Math.min(lTo, arrayResult.length); + + return Arrays.asList(Arrays.copyOfRange(arrayResult, lFrom, lTo)); + } + +} +/* JavaCC - OriginalChecksum=594a372e31fcbcd3ed962c2260e76468 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySelector.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySelector.java new file mode 100644 index 00000000000..11b0976e70d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySelector.java @@ -0,0 +1,61 @@ +/* Generated By:JJTree: Do not edit this line. OArraySelector.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; + +public class OArraySelector extends SimpleNode { + + protected ORid rid; + protected OInputParameter inputParam; + protected OExpression expression; + protected OInteger integer; + + public OArraySelector(int id) { + super(id); + } + + public OArraySelector(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (rid != null) { + rid.toString(params, builder); + } else if (inputParam != null) { + inputParam.toString(params, builder); + } else if (expression != null) { + expression.toString(params, builder); + } else if (integer != null) { + integer.toString(params, builder); + } + } + + public Integer getValue(OIdentifiable iCurrentRecord, Object iResult, OCommandContext ctx) { + Object result = null; + if (inputParam!= null) { + result = inputParam.bindFromInputParams(ctx.getInputParameters()); + } else if (expression != null) { + result = expression.execute(iCurrentRecord, ctx); + } else if (integer != null) { + result = integer; + } + + if (result == null) { + return null; + } + if (result instanceof Number) { + return ((Number) result).intValue(); + } + return null; + } +} +/* JavaCC - OriginalChecksum=f87a5543b1dad0fb5f6828a0663a7c9e (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySingleValuesSelector.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySingleValuesSelector.java new file mode 100644 index 00000000000..7ad565493db --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OArraySingleValuesSelector.java @@ -0,0 +1,54 @@ +/* Generated By:JJTree: Do not edit this line. OArraySingleValuesSelector.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OArraySingleValuesSelector extends SimpleNode { + + protected List items = new ArrayList(); + + public OArraySingleValuesSelector(int id) { + super(id); + } + + public OArraySingleValuesSelector(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + boolean first = true; + for (OArraySelector item : items) { + if (!first) { + builder.append(","); + } + item.toString(params, builder); + first = false; + } + } + + public Object execute(OIdentifiable iCurrentRecord, Object iResult, OCommandContext ctx) { + List result = new ArrayList(); + for(OArraySelector item:items){ + Integer index = item.getValue(iCurrentRecord, iResult, ctx); + if(this.items.size()==1){ + return OMultiValue.getValue(iResult, index); + } + result.add(OMultiValue.getValue(iResult, index)); + } + return result; + } + +} +/* JavaCC - OriginalChecksum=991998c77a4831184b6dca572513fd8d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseExpression.java new file mode 100644 index 00000000000..af49f464cbf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseExpression.java @@ -0,0 +1,131 @@ +/* Generated By:JJTree: Do not edit this line. OBaseExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OBaseExpression extends OMathExpression { + + private static final Object UNSET = new Object(); + private Object inputFinalValue = UNSET; + + protected ONumber number; + + protected OBaseIdentifier identifier; + + protected OInputParameter inputParam; + + protected String string; + + OModifier modifier; + + public OBaseExpression(int id) { + super(id); + } + + public OBaseExpression(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public String toString() { + return super.toString(); + } + + public void toString(Map params, StringBuilder builder) { + if (number != null) { + number.toString(params, builder); + } else if (identifier != null) { + identifier.toString(params, builder); + } else if (string != null) { + builder.append(string); + } else if (inputParam != null) { + inputParam.toString(params, builder); + } + + if (modifier != null) { + modifier.toString(params, builder); + } + + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + Object result = null; + if (number != null) { + result = number.getValue(); + } + if (identifier != null) { + result = identifier.execute(iCurrentRecord, ctx); + } + if (string != null && string.length() > 1) { + result = OStringSerializerHelper.decode(string.substring(1, string.length() - 1)); + } + if (modifier != null) { + result = modifier.execute(iCurrentRecord, result, ctx); + } + return result; + } + + @Override protected boolean supportsBasicCalculation() { + return true; + } + + @Override public boolean isIndexedFunctionCall() { + if (this.identifier == null) { + return false; + } + return identifier.isIndexedFunctionCall(); + } + + public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) { + if (this.identifier == null) { + return -1; + } + return identifier.estimateIndexedFunction(target, context, operator, right); + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context, + OBinaryCompareOperator operator, Object right) { + if (this.identifier == null) { + return null; + } + return identifier.executeIndexedFunction(target, context, operator, right); + } + + @Override public boolean isBaseIdentifier() { + return identifier != null && modifier == null && identifier.isBaseIdentifier(); + } + + public OIdentifier getBaseIdentifier() { + return identifier.suffix.identifier; + } + + public boolean isEarlyCalculated() { + if (number != null || inputParam != null || string != null) { + return true; + } + return false; + } + + public List getMatchPatternInvolvedAliases() { + if (this.identifier != null && this.identifier.toString().equals("$matched")) { + if (modifier != null && modifier.suffix != null && modifier.suffix.identifier != null) { + return Collections.singletonList(modifier.suffix.identifier.toString()); + } + } + return null; + } +} +/* JavaCC - OriginalChecksum=71b3e2d1b65c923dc7cfe11f9f449d2b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseIdentifier.java new file mode 100644 index 00000000000..2893c43dfa9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBaseIdentifier.java @@ -0,0 +1,76 @@ +/* Generated By:JJTree: Do not edit this line. OBaseIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; + +public class OBaseIdentifier extends SimpleNode { + + protected OLevelZeroIdentifier levelZero; + + protected OSuffixIdentifier suffix; + + public OBaseIdentifier(int id) { + super(id); + } + + public OBaseIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (levelZero != null) { + levelZero.toString(params, builder); + } else if (suffix != null) { + suffix.toString(params, builder); + } + } + + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (levelZero != null) { + return levelZero.execute(iCurrentRecord, ctx); + } + if (suffix != null) { + return suffix.execute(iCurrentRecord, ctx); + } + return null; + } + + public boolean isIndexedFunctionCall() { + if(levelZero!=null){ + return levelZero.isIndexedFunctionCall(); + } + return false; + } + + public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) { + if(levelZero!=null){ + return levelZero.estimateIndexedFunction(target, context, operator, right); + } + + return -1; + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context, + OBinaryCompareOperator operator, Object right) { + if(levelZero!=null){ + return levelZero.executeIndexedFunction(target, context, operator, right); + } + + return null; + } + + public boolean isBaseIdentifier() { + return suffix!=null && suffix.isBaseIdentifier(); + } +} +/* JavaCC - OriginalChecksum=ed89af10d8be41a83428c5608a4834f6 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBatch.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBatch.java new file mode 100644 index 00000000000..07f71fed752 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBatch.java @@ -0,0 +1,43 @@ +/* Generated By:JJTree: Do not edit this line. OBatch.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OBatch extends SimpleNode { + + protected OInteger num; + + protected OInputParameter inputParam; + + + public OBatch(int id) { + super(id); + } + + public OBatch(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + if (num == null && inputParam == null) { + return; + } + + builder.append(" BATCH "); + if (num != null) { + num.toString(params, builder); + } else { + inputParam.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=b1587460e08cbf21086d8c8fcca192e0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBeginStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBeginStatement.java new file mode 100644 index 00000000000..db94cf0b283 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBeginStatement.java @@ -0,0 +1,26 @@ +/* Generated By:JJTree: Do not edit this line. OBeginStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OBeginStatement extends OStatement { + protected OIdentifier isolation; + public OBeginStatement(int id) { + super(id); + } + + public OBeginStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("BEGIN"); + if(isolation!=null){ + builder.append(" ISOLATION "); + isolation.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=aaa994acbe63cc4169fe33144d412fed (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBetweenCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBetweenCondition.java new file mode 100644 index 00000000000..6b3bf9db783 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBetweenCondition.java @@ -0,0 +1,125 @@ +/* Generated By:JJTree: Do not edit this line. OBetweenCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OBetweenCondition extends OBooleanExpression{ + + protected OExpression first; + protected OExpression second; + protected OExpression third; + + public OBetweenCondition(int id) { + super(id); + } + + public OBetweenCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + Object firstValue = first.execute(currentRecord, ctx); + if (firstValue == null) { + return false; + } + + Object secondValue = second.execute(currentRecord, ctx); + if (secondValue == null) { + return false; + } + + secondValue = OType.convert(secondValue, firstValue.getClass()); + + Object thirdValue = third.execute(currentRecord, ctx); + if (thirdValue == null) { + return false; + } + thirdValue = OType.convert(thirdValue, firstValue.getClass()); + + final int leftResult = ((Comparable) firstValue).compareTo(secondValue); + final int rightResult = ((Comparable) firstValue).compareTo(thirdValue); + + return leftResult >= 0 && rightResult <= 0; + } + + public OExpression getFirst() { + return first; + } + + public void setFirst(OExpression first) { + this.first = first; + } + + public OExpression getSecond() { + return second; + } + + public void setSecond(OExpression second) { + this.second = second; + } + + public OExpression getThird() { + return third; + } + + public void setThird(OExpression third) { + this.third = third; + } + + public void toString(Map params, StringBuilder builder) { + first.toString(params, builder); + builder.append(" BETWEEN "); + second.toString(params, builder); + builder.append(" AND "); + third.toString(params, builder); + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + return 0; + } + + @Override protected List getExternalCalculationConditions() { + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + List result = new ArrayList(); + List x = first.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + x = second.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + x = third.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + + if (result.size() == 0) { + return null; + } + return result; + } +} +/* JavaCC - OriginalChecksum=f94f4779c4a6c6d09539446045ceca89 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCompareOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCompareOperator.java new file mode 100644 index 00000000000..c8110941995 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCompareOperator.java @@ -0,0 +1,10 @@ +package com.orientechnologies.orient.core.sql.parser; + +/** + * Created by luigidellaquila on 12/11/14. + */ +public interface OBinaryCompareOperator { + public boolean execute(Object left, Object right); + + boolean supportsBasicCalculation(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCondition.java new file mode 100644 index 00000000000..4c79f0bdd69 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBinaryCondition.java @@ -0,0 +1,121 @@ +/* Generated By:JJTree: Do not edit this line. OBinaryCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OBinaryCondition extends OBooleanExpression{ + protected OExpression left; + protected OBinaryCompareOperator operator; + protected OExpression right; + + public OBinaryCondition(int id) { + super(id); + } + + public OBinaryCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return operator.execute(left.execute(currentRecord, ctx), right.execute(currentRecord, ctx)); + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" "); + builder.append(operator.toString()); + builder.append(" "); + right.toString(params, builder); + } + + protected boolean supportsBasicCalculation() { + if (!operator.supportsBasicCalculation()) { + return false; + } + return left.supportsBasicCalculation() && right.supportsBasicCalculation(); + + } + + @Override protected int getNumberOfExternalCalculations() { + int total = 0; + if (!operator.supportsBasicCalculation()) { + total++; + } + if (!left.supportsBasicCalculation()) { + total++; + } + if (!right.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + if (!operator.supportsBasicCalculation()) { + result.add(this); + } + if (!left.supportsBasicCalculation()) { + result.add(left); + } + if (!right.supportsBasicCalculation()) { + result.add(right); + } + return result; + } + + public OBinaryCondition isIndexedFunctionCondition(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (left.isIndexedFunctionCal()) { + return this; + } + return null; + } + + public long estimateIndexed(OFromClause target, OCommandContext context) { + return left.estimateIndexedFunction(target, context, operator, right.execute(null, context)); + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context) { + return left.executeIndexedFunction(target, context, operator, right.execute(null, context)); + } + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (left.isIndexedFunctionCal()) { + return Collections.singletonList(this); + } + return null; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left.getMatchPatternInvolvedAliases(); + List rightX = right.getMatchPatternInvolvedAliases(); + if (leftX == null) { + return rightX; + } + if (rightX == null) { + return leftX; + } + + List result = new ArrayList(); + result.addAll(leftX); + result.addAll(rightX); + return result; + } +} +/* JavaCC - OriginalChecksum=99ed1dd2812eb730de8e1931b1764da5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBooleanExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBooleanExpression.java new file mode 100644 index 00000000000..c945307ffec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBooleanExpression.java @@ -0,0 +1,129 @@ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Created by luigidellaquila on 07/11/14. + */ +public abstract class OBooleanExpression extends SimpleNode { + + public static final OBooleanExpression TRUE = new OBooleanExpression(0) { + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return true; + } + + @Override protected boolean supportsBasicCalculation() { + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + return 0; + } + + @Override protected List getExternalCalculationConditions() { + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return null; + } + + @Override public String toString() { + return "true"; + } + + public void toString(Map params, StringBuilder builder) { + builder.append("true"); + } + }; + + public static final OBooleanExpression FALSE = new OBooleanExpression(0) { + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + @Override protected boolean supportsBasicCalculation() { + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + return 0; + } + + @Override protected List getExternalCalculationConditions() { + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return null; + } + + @Override public String toString() { + return "false"; + } + + public void toString(Map params, StringBuilder builder) { + builder.append("false"); + } + + }; + + public OBooleanExpression(int id) { + super(id); + } + + public OBooleanExpression(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public abstract boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx); + + /** + * @return true if this expression can be calculated in plain Java, false otherwise (eg. LUCENE operator) + */ + protected abstract boolean supportsBasicCalculation(); + + /** + * @return the number of sub-expressions that have to be calculated using an external engine (eg. LUCENE) + */ + protected abstract int getNumberOfExternalCalculations(); + + /** + * @return the sub-expressions that have to be calculated using an external engine (eg. LUCENE) + */ + protected abstract List getExternalCalculationConditions(); + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + return null; + } + + public List flatten() { + + return Collections.singletonList(encapsulateInAndBlock(this)); + } + + protected OAndBlock encapsulateInAndBlock(OBooleanExpression item) { + if (item instanceof OAndBlock) { + return (OAndBlock) item; + } + OAndBlock result = new OAndBlock(-1); + result.subBlocks.add(item); + return result; + } + + public abstract List getMatchPatternInvolvedAliases(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItem.java new file mode 100644 index 00000000000..f6bd4ae2458 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItem.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OBothPathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OBothPathItem extends OMatchPathItem { + public OBothPathItem(int id) { + super(id); + } + + public OBothPathItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("-"); + boolean first = true; + if (this.method.params != null) { + for (OExpression exp : this.method.params) { + if (!first) { + builder.append(", "); + } + builder.append(exp.execute(null, null)); + first = false; + } + } + builder.append("-"); + if (filter != null) { + filter.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=061ff26f18cfa0c561ce9b98ef919173 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItemOpt.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItemOpt.java new file mode 100644 index 00000000000..4f0091f5387 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OBothPathItemOpt.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. OBothPathItemOpt.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OBothPathItemOpt extends OBothPathItem { + public OBothPathItemOpt(int id) { + super(id); + } + + public OBothPathItemOpt(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=96af673f114382e530f23ae7937cb201 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCluster.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCluster.java new file mode 100644 index 00000000000..7e2a4ca34a2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCluster.java @@ -0,0 +1,37 @@ +/* Generated By:JJTree: Do not edit this line. OCluster.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCluster extends SimpleNode { + protected String clusterName; + protected Integer clusterNumber; + + public OCluster(int id) { + super(id); + } + + public OCluster(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public String toString(String prefix) { + return super.toString(prefix); + } + + public void toString(Map params, StringBuilder builder) { + if(clusterName!=null) { + builder.append("cluster:" + clusterName); + }else{ + builder.append("cluster:" + clusterNumber); + } + } +} +/* JavaCC - OriginalChecksum=d27abf009fe7db482fbcaac9d52ba192 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OClusterList.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OClusterList.java new file mode 100644 index 00000000000..2b7e2986800 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OClusterList.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OClusterList.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OClusterList extends SimpleNode { + + protected List clusters = new ArrayList(); + + public OClusterList(int id) { + super(id); + } + + public OClusterList(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + + builder.append("cluster:["); + boolean first = true; + for (OIdentifier id : clusters) { + if (!first) { + builder.append(","); + } + id.toString(params, builder); + first = false; + } + builder.append("]"); + } +} +/* JavaCC - OriginalChecksum=bd90ffa0b9d17f204b3cf2d47eedb409 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCollection.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCollection.java new file mode 100644 index 00000000000..bda5532ef67 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCollection.java @@ -0,0 +1,49 @@ +/* Generated By:JJTree: Do not edit this line. OCollection.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OCollection extends SimpleNode { + protected List expressions = new ArrayList(); + + public OCollection(int id) { + super(id); + } + + public OCollection(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("["); + boolean first = true; + for (OExpression expr : expressions) { + if (!first) { + builder.append(", "); + } + expr.toString(params, builder); + first = false; + } + builder.append("]"); + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + List result = new ArrayList(); + for (OExpression exp : expressions) { + result.add(exp.execute(iCurrentRecord, ctx)); + } + return result; + } +} +/* JavaCC - OriginalChecksum=c93b20138b2ae58c5f76e458c34b5946 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommandLineOption.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommandLineOption.java new file mode 100644 index 00000000000..bca3432fefa --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommandLineOption.java @@ -0,0 +1,25 @@ +/* Generated By:JJTree: Do not edit this line. OCommandLineOption.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OCommandLineOption extends SimpleNode { + + protected OIdentifier name; + public OCommandLineOption(int id) { + super(id); + } + + public OCommandLineOption(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("-"); + name.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=7fcb8de8a1f99a2737aac85933d074d9 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommitStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommitStatement.java new file mode 100644 index 00000000000..b397db0f8e0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCommitStatement.java @@ -0,0 +1,27 @@ +/* Generated By:JJTree: Do not edit this line. OCommitStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCommitStatement extends OStatement { + + protected OInteger retry; + + public OCommitStatement(int id) { + super(id); + } + + public OCommitStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("COMMIT"); + if (retry != null) { + builder.append(" RETRY "); + retry.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=eaa0bc8f765fdaa017789953861bc0aa (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCompareOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCompareOperator.java new file mode 100644 index 00000000000..5591f29fa7e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCompareOperator.java @@ -0,0 +1,22 @@ +/* Generated By:JJTree: Do not edit this line. OCompareOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OCompareOperator extends SimpleNode { + public OCompareOperator(int id) { + super(id); + } + + public OCompareOperator(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + +} +/* JavaCC - OriginalChecksum=aeef93fd1b053c63a8b92a979ac225df (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConditionBlock.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConditionBlock.java new file mode 100644 index 00000000000..2039b24127d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConditionBlock.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OConditionBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OConditionBlock extends SimpleNode { + public OConditionBlock(int id) { + super(id); + } + + public OConditionBlock(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=d3e0589119a7b64cf9891d6baaf9e449 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConsoleStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConsoleStatement.java new file mode 100644 index 00000000000..2060bd9ac6d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OConsoleStatement.java @@ -0,0 +1,26 @@ +/* Generated By:JJTree: Do not edit this line. OConsoleStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OConsoleStatement extends OStatement { + protected OIdentifier logLevel; + protected OExpression message; + + public OConsoleStatement(int id) { + super(id); + } + + public OConsoleStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("CONSOLE."); + logLevel.toString(params, builder); + builder.append(" "); + message.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=626c09cda52a1a8a63eeefcb37bd66a1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsAllCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsAllCondition.java new file mode 100644 index 00000000000..d879002b747 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsAllCondition.java @@ -0,0 +1,127 @@ +/* Generated By:JJTree: Do not edit this line. OContainsAllCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OContainsAllCondition extends OBooleanExpression { + + protected OExpression left; + + protected OExpression right; + + protected OOrBlock rightBlock; + + public OContainsAllCondition(int id) { + super(id); + } + + public OContainsAllCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false;// TODO + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" CONTAINSALL "); + if (right != null) { + right.toString(params, builder); + } else if (rightBlock != null) { + builder.append("("); + rightBlock.toString(params, builder); + builder.append(")"); + } + } + + public OExpression getLeft() { + return left; + } + + public void setLeft(OExpression left) { + this.left = left; + } + + public OExpression getRight() { + return right; + } + + public void setRight(OExpression right) { + this.right = right; + } + + @Override public boolean supportsBasicCalculation() { + if (left != null && !left.supportsBasicCalculation()) { + return false; + } + if (right != null && !right.supportsBasicCalculation()) { + return false; + } + if (rightBlock != null && !rightBlock.supportsBasicCalculation()) { + return false; + } + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + int total = 0; + if (left != null && !left.supportsBasicCalculation()) { + total++; + } + if (right != null && !right.supportsBasicCalculation()) { + total++; + } + if (rightBlock != null && !rightBlock.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + if (left != null && !left.supportsBasicCalculation()) { + result.add(left); + } + if (right != null && !right.supportsBasicCalculation()) { + result.add(right); + } + if (rightBlock != null) { + result.addAll(rightBlock.getExternalCalculationConditions()); + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + List rightX = right == null ? null : right.getMatchPatternInvolvedAliases(); + List rightBlockX = rightBlock == null ? null : rightBlock.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (rightX != null) { + result.addAll(rightX); + } + if (rightBlockX != null) { + result.addAll(rightBlockX); + } + + return result.size() == 0 ? null : result; + } +} +/* JavaCC - OriginalChecksum=ab7b4e192a01cda09a82d5b80ef4ec60 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsCondition.java new file mode 100644 index 00000000000..6360f30a0fd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsCondition.java @@ -0,0 +1,162 @@ +/* Generated By:JJTree: Do not edit this line. OContainsCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.*; + +public class OContainsCondition extends OBooleanExpression { + + protected OExpression left; + protected OExpression right; + protected OBooleanExpression condition; + + public OContainsCondition(int id) { + super(id); + } + + public OContainsCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean execute(Object left, Object right) { + if (left instanceof Collection) { + if (right instanceof Collection) { + return ((Collection) left).containsAll((Collection) right); + } + if (right instanceof Iterable) { + right = ((Iterable) right).iterator(); + } + if (right instanceof Iterator) { + Iterator iterator = (Iterator) right; + while (iterator.hasNext()) { + Object next = iterator.next(); + if (!((Collection) left).contains(next)) { + return false; + } + } + } + return ((Collection) left).contains(right); + } + if (left instanceof Iterable) { + left = ((Iterable) left).iterator(); + } + if (left instanceof Iterator) { + if (!(right instanceof Iterable)) { + right = Collections.singleton(right); + } + right = ((Iterable) right).iterator(); + + Iterator leftIterator = (Iterator) left; + Iterator rightIterator = (Iterator) right; + while (rightIterator.hasNext()) { + Object leftItem = rightIterator.next(); + boolean found = false; + while (leftIterator.hasNext()) { + Object rightItem = leftIterator.next(); + if (leftItem != null && leftItem.equals(rightItem)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + return false; + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + Object leftValue = left.execute(currentRecord, ctx); + Object rightValue = right.execute(currentRecord, ctx); + return execute(leftValue, rightValue); + + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" CONTAINS "); + if (right != null) { + right.toString(params, builder); + } else if (condition != null) { + builder.append("("); + condition.toString(params, builder); + builder.append(")"); + } + } + + @Override public boolean supportsBasicCalculation() { + if (!left.supportsBasicCalculation()) { + return false; + } + if (!right.supportsBasicCalculation()) { + return false; + } + if (!condition.supportsBasicCalculation()) { + return false; + } + + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + int total = 0; + if (condition != null) { + total += condition.getNumberOfExternalCalculations(); + } + if (!left.supportsBasicCalculation()) { + total++; + } + if (right != null && !right.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + + if (condition != null) { + result.addAll(condition.getExternalCalculationConditions()); + } + if (!left.supportsBasicCalculation()) { + result.add(left); + } + if (right != null && !right.supportsBasicCalculation()) { + result.add(right); + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + List rightX = right == null ? null : right.getMatchPatternInvolvedAliases(); + List conditionX = condition == null ? null : condition.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (rightX != null) { + result.addAll(rightX); + } + if (conditionX != null) { + result.addAll(conditionX); + } + + return result.size() == 0 ? null : result; + } + +} +/* JavaCC - OriginalChecksum=bad1118296ea74860e88d66bfe9fa222 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsKeyOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsKeyOperator.java new file mode 100644 index 00000000000..9306b67a691 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsKeyOperator.java @@ -0,0 +1,44 @@ +/* Generated By:JJTree: Do not edit this line. OContainsKeyOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OContainsKeyOperator extends SimpleNode implements OBinaryCompareOperator { + public OContainsKeyOperator(int id) { + super(id); + } + + public OContainsKeyOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + if (left == null) { + return false; + } + if (left instanceof Map) { + final Map map = (Map) left; + return map.containsKey(right); + } + return false; + } + + @Override + public String toString() { + return "CONTAINSKEY"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=1a03daaa6712eb981b070e8e94960951 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java new file mode 100644 index 00000000000..89dd92f30dc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java @@ -0,0 +1,85 @@ +/* Generated By:JJTree: Do not edit this line. OContainsTextCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OContainsTextCondition extends OBooleanExpression { + + protected OExpression left; + protected OExpression right; + + public OContainsTextCondition(int id) { + super(id); + } + + public OContainsTextCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" CONTAINSTEXT "); + right.toString(params, builder); + } + + @Override + public boolean supportsBasicCalculation() { + return true; + } + + @Override + protected int getNumberOfExternalCalculations() { + int total = 0; + if (!left.supportsBasicCalculation()) { + total++; + } + if (!right.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override + protected List getExternalCalculationConditions() { + List result = new ArrayList(); + if (!left.supportsBasicCalculation()) { + result.add(left); + } + if (!right.supportsBasicCalculation()) { + result.add(right); + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + List rightX = right == null ? null : right.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (rightX != null) { + result.addAll(rightX); + } + + return result.size() == 0 ? null : result; + } +} +/* JavaCC - OriginalChecksum=b588492ba2cbd0f932055f1f64bbbecd (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueCondition.java new file mode 100644 index 00000000000..bcc70997e35 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueCondition.java @@ -0,0 +1,92 @@ +/* Generated By:JJTree: Do not edit this line. OContainsValueCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OContainsValueCondition extends OBooleanExpression { + protected OExpression left; + protected OContainsValueOperator operator; + protected OOrBlock condition; + protected OExpression expression; + + public OContainsValueCondition(int id) { + super(id); + } + + public OContainsValueCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + + public void toString(Map params, StringBuilder builder) { + + left.toString(params, builder); + builder.append(" CONTAINSVALUE "); + if (condition != null) { + builder.append("("); + condition.toString(params, builder); + builder.append(")"); + } else { + expression.toString(params, builder); + } + + } + + @Override + public boolean supportsBasicCalculation() { + return true; + } + + @Override + protected int getNumberOfExternalCalculations() { + if (condition == null) { + return 0; + } + return condition.getNumberOfExternalCalculations(); + } + + @Override + protected List getExternalCalculationConditions() { + if (condition == null) { + return Collections.EMPTY_LIST; + } + return condition.getExternalCalculationConditions(); + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + List expressionX = expression == null ? null : expression.getMatchPatternInvolvedAliases(); + List conditionX = condition == null ? null : condition.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (expressionX != null) { + result.addAll(expressionX); + } + if (conditionX != null) { + result.addAll(conditionX); + } + + return result.size() == 0 ? null : result; + } +} +/* JavaCC - OriginalChecksum=6fda752f10c8d8731f43efa706e39459 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueOperator.java new file mode 100644 index 00000000000..d630c3815dd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsValueOperator.java @@ -0,0 +1,41 @@ +/* Generated By:JJTree: Do not edit this line. OContainsValueOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OContainsValueOperator extends SimpleNode implements OBinaryCompareOperator { + public OContainsValueOperator(int id) { + super(id); + } + + public OContainsValueOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + if (iLeft instanceof Map) { + final Map map = (Map) iLeft; + return map.containsValue(iRight); + } + return false; + + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + @Override + public String toString() { + return "CONTAINSVALUE"; + } + +} +/* JavaCC - OriginalChecksum=5d6492dbb028b8bac69e60d4916cf341 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClassStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClassStatement.java new file mode 100644 index 00000000000..f4c48896699 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClassStatement.java @@ -0,0 +1,83 @@ +/* Generated By:JJTree: Do not edit this line. OCreateClassStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OCreateClassStatement extends OStatement { + /** + * Class name + */ + public OIdentifier name; + + boolean ifNotExists = false; + + /** + * Direct superclasses for this class + */ + protected List superclasses; + + /** + * Cluster IDs for this class + */ + protected List clusters; + + /** + *Total number clusters for this class + */ + protected OInteger totalClusterNo; + + protected boolean abstractClass = false; + + public OCreateClassStatement(int id) { + super(id); + } + + public OCreateClassStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE CLASS "); + name.toString(params, builder); + if(ifNotExists){ + builder.append(" IF NOT EXISTS"); + } + if (superclasses != null && superclasses.size() > 0) { + builder.append(" EXTENDS "); + boolean first = true; + for (OIdentifier sup : superclasses) { + if (!first) { + builder.append(", "); + } + sup.toString(params, builder); + first = false; + } + } + if (clusters != null && clusters.size() > 0) { + builder.append(" CLUSTER "); + boolean first = true; + for (OInteger cluster : clusters) { + if (!first) { + builder.append(","); + } + cluster.toString(params, builder); + first = false; + } + } + if(totalClusterNo!=null){ + builder.append(" CLUSTERS "); + totalClusterNo.toString(params, builder); + } + if(abstractClass){ + builder.append(" ABSTRACT"); + } + } + + public List getSuperclasses() { + return superclasses; + } +} +/* JavaCC - OriginalChecksum=4043013624f55fdf0ea8fee6d4f211b0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClusterStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClusterStatement.java new file mode 100644 index 00000000000..246f3e9b8ef --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateClusterStatement.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OCreateClusterStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreateClusterStatement extends OStatement { + + /** + * Class name + */ + protected OIdentifier name; + + protected OInteger id; + + protected boolean blob = false; + + public OCreateClusterStatement(int id) { + super(id); + } + + public OCreateClusterStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("CREATE "); + if(blob){ + builder.append("BLOB "); + } + builder.append("CLUSTER "); + name.toString(params, builder); + if (id != null) { + builder.append(" ID "); + id.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=6011a26678f2175aa456a0a6c094cb13 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateEdgeStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateEdgeStatement.java new file mode 100644 index 00000000000..8f2af8d82cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateEdgeStatement.java @@ -0,0 +1,65 @@ +/* Generated By:JJTree: Do not edit this line. OCreateEdgeStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreateEdgeStatement extends OStatement { + + private static final Object unset = new Object(); + + protected OIdentifier targetClass; + protected OIdentifier targetClusterName; + + protected OExpression leftExpression; + + protected OExpression rightExpression; + + protected OInsertBody body; + protected Number retry; + protected Number wait; + protected OBatch batch; + + public OCreateEdgeStatement(int id) { + super(id); + } + + public OCreateEdgeStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE EDGE"); + if (targetClass != null) { + builder.append(" "); + targetClass.toString(params, builder); + if (targetClusterName != null) { + builder.append(" CLUSTER "); + targetClusterName.toString(params, builder); + } + } + builder.append(" FROM "); + leftExpression.toString(params, builder); + + builder.append(" TO "); + rightExpression.toString(params, builder); + + if (body != null) { + builder.append(" "); + body.toString(params, builder); + } + if (retry != null) { + builder.append(" RETRY "); + builder.append(retry); + } + if (wait != null) { + builder.append(" WAIT "); + builder.append(wait); + } + if (batch != null) { + batch.toString(params, builder); + } + } + +} +/* JavaCC - OriginalChecksum=2d3dc5693940ffa520146f8f7f505128 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateFunctionStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateFunctionStatement.java new file mode 100644 index 00000000000..56ddcb8b889 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateFunctionStatement.java @@ -0,0 +1,53 @@ +/* Generated By:JJTree: Do not edit this line. OCreateFunctionStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OCreateFunctionStatement extends OStatement { + protected OIdentifier name; + protected String codeQuoted; + protected String code; + + protected List parameters; + protected Boolean idempotent; + protected OIdentifier language; + + public OCreateFunctionStatement(int id) { + super(id); + } + + public OCreateFunctionStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE FUNCTION "); + name.toString(params, builder); + builder.append(" "); + builder.append(codeQuoted); + if (parameters != null) { + boolean first = true; + builder.append(" PARAMETERS ["); + for (OIdentifier param : parameters) { + if (!first) { + builder.append(", "); + } + param.toString(params, builder); + first = false; + } + builder.append("]"); + } + if (idempotent != null) { + builder.append(" IDEMPOTENT "); + builder.append(idempotent ? "true" : "false"); + } + if (language != null) { + builder.append(" LANGUAGE "); + language.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=bbc914f66e96822dedc7e89e14240872 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateIndexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateIndexStatement.java new file mode 100644 index 00000000000..8e904d19514 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateIndexStatement.java @@ -0,0 +1,91 @@ +/* Generated By:JJTree: Do not edit this line. OCreateIndexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OCreateIndexStatement extends OStatement { + + public static class Property { + protected OIdentifier name; + protected ORecordAttribute recordAttribute; + protected OIdentifier className; + protected boolean byKey = false; + protected boolean byValue = false; + protected OIdentifier collate; + + } + + protected OIndexName name; + protected OIdentifier className; + protected List propertyList = new ArrayList(); + protected OIdentifier type; + protected OIdentifier engine; + protected List keyTypes = new ArrayList(); + protected OJson metadata; + + public OCreateIndexStatement(int id) { + super(id); + } + + public OCreateIndexStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE INDEX "); + name.toString(params, builder); + if (className != null) { + builder.append(" ON "); + className.toString(params, builder); + builder.append(" ("); + boolean first = true; + for (Property prop : propertyList) { + if (!first) { + builder.append(", "); + } + if(prop.name!=null) { + prop.name.toString(params, builder); + }else{ + prop.recordAttribute.toString(params, builder); + } + if (prop.byKey) { + builder.append(" BY KEY"); + } else if (prop.byValue) { + builder.append(" BY VALUE"); + } + if(prop.collate!=null){ + builder.append(" COLLATE "); + prop.collate.toString(params, builder); + } + first = false; + } + builder.append(")"); + } + builder.append(" "); + type.toString(params, builder); + if(engine!=null){ + builder.append(" ENGINE "); + engine.toString(params, builder); + } + if (keyTypes != null && keyTypes.size()>0) { + boolean first = true; + builder.append(" "); + for(OIdentifier keyType:keyTypes){ + if(!first){ + builder.append(","); + } + keyType.toString(params, builder); + first = false; + } + } + if (metadata != null) { + builder.append(" METADATA "); + metadata.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=bd090e02c4346ad390a6b8c77f1b9dba (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateLinkStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateLinkStatement.java new file mode 100644 index 00000000000..8c0e0a9309d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateLinkStatement.java @@ -0,0 +1,54 @@ +/* Generated By:JJTree: Do not edit this line. OCreateLinkStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreateLinkStatement extends OStatement { + + protected OIdentifier name; + protected OIdentifier type; + protected OIdentifier sourceClass; + protected OIdentifier sourceField; + protected ORecordAttribute sourceRecordAttr; + protected OIdentifier destClass; + protected OIdentifier destField; + protected ORecordAttribute destRecordAttr; + protected boolean inverse = false; + + public OCreateLinkStatement(int id) { + super(id); + } + + public OCreateLinkStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE LINK "); + name.toString(params, builder); + builder.append(" TYPE "); + type.toString(params, builder); + builder.append(" FROM "); + sourceClass.toString(params, builder); + builder.append("."); + if (sourceField != null) { + sourceField.toString(params, builder); + } else { + sourceRecordAttr.toString(params, builder); + } + builder.append(" TO "); + destClass.toString(params, builder); + builder.append("."); + if (destField != null) { + destField.toString(params, builder); + } else { + destRecordAttr.toString(params, builder); + } + if (inverse) { + builder.append(" INVERSE"); + } + } +} +/* JavaCC - OriginalChecksum=de46c9bdaf3b36691764a78cd89d1c2b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyAttributeStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyAttributeStatement.java new file mode 100644 index 00000000000..c22e26fdfc4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyAttributeStatement.java @@ -0,0 +1,33 @@ +/* Generated By:JJTree: Do not edit this line. OCreatePropertyAttributeStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreatePropertyAttributeStatement extends SimpleNode { + public OIdentifier settingName; + public OExpression settingValue; + + public OCreatePropertyAttributeStatement(int id) { + super(id); + } + + public OCreatePropertyAttributeStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + settingName.toString(params, builder); + if(settingValue!=null){ + builder.append(" "); + settingValue.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=6a7964c2b9dad541ca962eecea00651b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyStatement.java new file mode 100644 index 00000000000..bcdacafbf23 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreatePropertyStatement.java @@ -0,0 +1,60 @@ +/* Generated By:JJTree: Do not edit this line. OCreatePropertyStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OCreatePropertyStatement extends OStatement { + public OIdentifier className; + public OIdentifier propertyName; + boolean ifNotExists = false; + public OIdentifier propertyType; + public OIdentifier linkedType; + public boolean unsafe = false; + public List attributes = new ArrayList(); + + public OCreatePropertyStatement(int id) { + super(id); + } + + public OCreatePropertyStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("CREATE PROPERTY "); + className.toString(params, builder); + builder.append("."); + propertyName.toString(params, builder); + if(ifNotExists){ + builder.append(" IF NOT EXISTS"); + } + builder.append(" "); + propertyType.toString(params, builder); + if (linkedType != null) { + builder.append(" "); + linkedType.toString(params, builder); + } + + if (!attributes.isEmpty()) { + builder.append(" ("); + for (int i = 0; i < attributes.size(); i++) { + OCreatePropertyAttributeStatement att = attributes.get(i); + att.toString(params, builder); + + if (i < attributes.size() - 1) { + builder.append(", "); + } + } + builder.append(")"); + } + + if (unsafe) { + builder.append(" UNSAFE"); + } + } +} +/* JavaCC - OriginalChecksum=ff78676483d59013ab10b13bde2678d3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateSequenceStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateSequenceStatement.java new file mode 100644 index 00000000000..4113392c7d1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateSequenceStatement.java @@ -0,0 +1,54 @@ +/* Generated By:JJTree: Do not edit this line. OCreateSequenceStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreateSequenceStatement extends OStatement { + public static final int TYPE_CACHED = 0; + public static final int TYPE_ORDERED = 1; + + OIdentifier name; + int type; + OExpression start; + OExpression increment; + OExpression cache; + + public OCreateSequenceStatement(int id) { + super(id); + } + + public OCreateSequenceStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("CREATE SEQUENCE "); + name.toString(params, builder); + builder.append(" TYPE "); + switch (type) { + case TYPE_CACHED: + builder.append(" CACHED"); + break; + case TYPE_ORDERED: + builder.append(" ORDERED"); + break; + default: + throw new IllegalStateException("Invalid type for CREATE SEQUENCE: " + type); + } + + if (start != null) { + builder.append(" START "); + start.toString(params, builder); + } + if (increment != null) { + builder.append(" INCREMENT "); + increment.toString(params, builder); + } + if (cache != null) { + builder.append(" CACHE "); + cache.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=b0436d11e05c3435f22dafea6b5106c0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexNoTargetStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexNoTargetStatement.java new file mode 100644 index 00000000000..fa64d5f785e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexNoTargetStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OCreateVertexNoTargetStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OCreateVertexNoTargetStatement extends OCreateVertexStatement { + public OCreateVertexNoTargetStatement(int id) { + super(id); + } + + public OCreateVertexNoTargetStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=25d9cdfd149e7b374a84dfd166e11306 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatement.java new file mode 100644 index 00000000000..2ec514ae7f7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatement.java @@ -0,0 +1,49 @@ +/* Generated By:JJTree: Do not edit this line. OCreateVertexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OCreateVertexStatement extends OStatement { + + OIdentifier targetClass; + OIdentifier targetClusterName; + OCluster targetCluster; + OProjection returnStatement; + OInsertBody insertBody; + + public OCreateVertexStatement(int id) { + super(id); + } + + public OCreateVertexStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + + builder.append("CREATE VERTEX "); + if (targetClass != null) { + targetClass.toString(params, builder); + if (targetClusterName != null) { + builder.append(" CLUSTER "); + targetClusterName.toString(params, builder); + } + } + if (targetCluster != null) { + targetCluster.toString(params, builder); + } + if (returnStatement != null) { + builder.append(" RETURN "); + returnStatement.toString(params, builder); + } + if (insertBody != null) { + if (targetClass != null || targetCluster != null || returnStatement != null) { + builder.append(" "); + } + insertBody.toString(params, builder); + } + } + +} +/* JavaCC - OriginalChecksum=0ac3d3f09a76b9924a17fd05bc293863 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmpty.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmpty.java new file mode 100644 index 00000000000..84927004e62 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmpty.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OCreateVertexStatementEmpty.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OCreateVertexStatementEmpty extends OCreateVertexStatement { + public OCreateVertexStatementEmpty(int id) { + super(id); + } + + public OCreateVertexStatementEmpty(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=016e27188dd8fae2a4e129ad660c5c23 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmptyNoTarget.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmptyNoTarget.java new file mode 100644 index 00000000000..7ac04190ac3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementEmptyNoTarget.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OCreateVertexStatementEmptyNoTarget.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OCreateVertexStatementEmptyNoTarget extends OCreateVertexStatement { + public OCreateVertexStatementEmptyNoTarget(int id) { + super(id); + } + + public OCreateVertexStatementEmptyNoTarget(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=e8507ab0b0c002964e04813d45ee71a0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementNoTarget.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementNoTarget.java new file mode 100644 index 00000000000..befb62bfcf8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OCreateVertexStatementNoTarget.java @@ -0,0 +1,16 @@ +/* Generated By:JJTree: Do not edit this line. OCreateVertexStatementNoTarget.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OCreateVertexStatementNoTarget extends OCreateVertexStatement { + public OCreateVertexStatementNoTarget(int id) { + super(id); + } + + public OCreateVertexStatementNoTarget(OrientSql p, int id) { + super(p, id); + } + +} +/* JavaCC - OriginalChecksum=5213b77f14f5f89255590034bdc0ea54 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeByRidStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeByRidStatement.java new file mode 100644 index 00000000000..a48705a0b19 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeByRidStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeByRidStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ODeleteEdgeByRidStatement extends ODeleteEdgeStatement { + public ODeleteEdgeByRidStatement(int id) { + super(id); + } + + public ODeleteEdgeByRidStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=98dcdb9b472b04699d1a2bd35f9e54a6 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeFromToStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeFromToStatement.java new file mode 100644 index 00000000000..4ad8318f2e7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeFromToStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeFromToStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ODeleteEdgeFromToStatement extends ODeleteEdgeStatement { + public ODeleteEdgeFromToStatement(int id) { + super(id); + } + + public ODeleteEdgeFromToStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=ca4781ee373b544b84bd6be28dba3ad5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeStatement.java new file mode 100644 index 00000000000..952d85b995e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeStatement.java @@ -0,0 +1,147 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class ODeleteEdgeStatement extends OStatement { + private static final Object unset = new Object(); + + protected OIdentifier className; + protected OIdentifier targetClusterName; + + protected ORid rid; + protected List rids; + + protected ORid leftRid; + protected List leftRids; + protected OSelectStatement leftStatement; + protected OInputParameter leftParam; + protected Object leftParamValue = unset; + protected OIdentifier leftIdentifier; + + protected ORid rightRid; + protected List rightRids; + protected OSelectStatement rightStatement; + protected OInputParameter rightParam; + protected Object rightParamValue = unset; + protected OIdentifier rightIdentifier; + + protected OWhereClause whereClause; + + protected OLimit limit; + protected OBatch batch = null; + + public ODeleteEdgeStatement(int id) { + super(id); + } + + public ODeleteEdgeStatement(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("DELETE EDGE"); + + if (className != null) { + builder.append(" "); + className.toString(params, builder); + if (targetClusterName != null) { + builder.append(" CLUSTER "); + targetClusterName.toString(params, builder); + } + } + + if (rid != null) { + builder.append(" "); + rid.toString(params, builder); + } + if (rids != null) { + builder.append(" ["); + boolean first = true; + for (ORid rid : rids) { + if (!first) { + builder.append(", "); + } + rid.toString(params, builder); + first = false; + } + builder.append("]"); + } + + if (leftRid != null || leftRids != null || leftStatement != null || leftParam != null || leftIdentifier != null) { + builder.append(" FROM "); + if (leftRid != null) { + leftRid.toString(params, builder); + } else if (leftRids != null) { + builder.append("["); + boolean first = true; + for (ORid rid : leftRids) { + if (!first) { + builder.append(", "); + } + rid.toString(params, builder); + first = false; + } + builder.append("]"); + } else if (leftStatement != null) { + builder.append("("); + leftStatement.toString(params, builder); + builder.append(")"); + } else if (leftParam != null) { + leftParam.toString(params, builder); + } else if (leftIdentifier != null) { + leftIdentifier.toString(params, builder); + } + + } + if (rightRid != null || rightRids != null || rightStatement != null || rightParam != null || rightIdentifier != null) { + builder.append(" TO "); + if (rightRid != null) { + rightRid.toString(params, builder); + } else if (rightRids != null) { + builder.append("["); + boolean first = true; + for (ORid rid : rightRids) { + if (!first) { + builder.append(", "); + } + rid.toString(params, builder); + first = false; + } + builder.append("]"); + } else if (rightStatement != null) { + builder.append("("); + rightStatement.toString(params, builder); + builder.append(")"); + } else if (rightParam != null) { + rightParam.toString(params, builder); + } else if (rightIdentifier != null) { + rightIdentifier.toString(params, builder); + } + } + + if (whereClause != null) { + builder.append(" WHERE "); + whereClause.toString(params, builder); + } + + if (limit != null) { + limit.toString(params, builder); + } + if (batch != null) { + batch.toString(params, builder); + } + } + + public OWhereClause getWhereClause() { + return whereClause; + } +} +/* JavaCC - OriginalChecksum=8f4c5bafa99572d7d87a5d0a2c7d55a7 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeToStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeToStatement.java new file mode 100644 index 00000000000..1a8b3b2e6a9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeToStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeToStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ODeleteEdgeToStatement extends ODeleteEdgeStatement { + public ODeleteEdgeToStatement(int id) { + super(id); + } + + public ODeleteEdgeToStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=8b71c6e3bc7262af9a8e0e0ea3a1964c (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeVToStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeVToStatement.java new file mode 100644 index 00000000000..363986df1dd --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeVToStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeVToStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ODeleteEdgeVToStatement extends ODeleteEdgeStatement { + public ODeleteEdgeVToStatement(int id) { + super(id); + } + + public ODeleteEdgeVToStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=c5435be513de4871657bae94ae2a126b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeWhereStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeWhereStatement.java new file mode 100644 index 00000000000..7fda3360d57 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteEdgeWhereStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteEdgeWhereStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ODeleteEdgeWhereStatement extends ODeleteEdgeStatement { + public ODeleteEdgeWhereStatement(int id) { + super(id); + } + + public ODeleteEdgeWhereStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=1298a0baf9921378983d0722f8ebe68b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteStatement.java new file mode 100644 index 00000000000..132d09d5c19 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteStatement.java @@ -0,0 +1,43 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ODeleteStatement extends OStatement { + + public OFromClause fromClause; + protected OWhereClause whereClause; + protected boolean returnBefore = false; + protected OLimit limit = null; + protected boolean unsafe = false; + + public ODeleteStatement(int id) { + super(id); + } + + public ODeleteStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + + builder.append("DELETE FROM "); + fromClause.toString(params, builder); + if (returnBefore) { + builder.append(" RETURN BEFORE"); + } + if (whereClause != null) { + builder.append(" WHERE "); + whereClause.toString(params, builder); + } + if (limit != null) { + limit.toString(params, builder); + } + if (unsafe) { + builder.append(" UNSAFE"); + } + } + +} +/* JavaCC - OriginalChecksum=5fb4ca5ba648e6c9110f41d806206a6f (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteVertexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteVertexStatement.java new file mode 100644 index 00000000000..3bc4f74004b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODeleteVertexStatement.java @@ -0,0 +1,47 @@ +/* Generated By:JJTree: Do not edit this line. ODeleteVertexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ODeleteVertexStatement extends OStatement { + + protected boolean from = false; + protected OFromClause fromClause; + protected OWhereClause whereClause; + protected boolean returnBefore = false; + protected OLimit limit = null; + protected OBatch batch = null; + + public ODeleteVertexStatement(int id) { + super(id); + } + + public ODeleteVertexStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("DELETE VERTEX "); + if(from){ + builder.append("FROM "); + } + fromClause.toString(params, builder); + if (returnBefore) { + builder.append(" RETURN BEFORE"); + } + if (whereClause != null) { + builder.append(" WHERE "); + whereClause.toString(params, builder); + } + if (limit != null) { + limit.toString(params, builder); + } + if (batch != null) { + batch.toString(params, builder); + } + } + + +} +/* JavaCC - OriginalChecksum=b62d3046f4bd1b9c1f78ed4f125b06d3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClassStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClassStatement.java new file mode 100644 index 00000000000..2dcd181ad8b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClassStatement.java @@ -0,0 +1,32 @@ +/* Generated By:JJTree: Do not edit this line. ODropClassStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ODropClassStatement extends OStatement { + + public OIdentifier name; + public boolean unsafe = false; + public boolean ifExists = false; + + public ODropClassStatement(int id) { + super(id); + } + + public ODropClassStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("DROP CLASS "); + name.toString(params, builder); + if (ifExists) { + builder.append(" IF EXISTS"); + } + if (unsafe) { + builder.append(" UNSAFE"); + } + } +} +/* JavaCC - OriginalChecksum=8c475e1225074f68be37fce610987d54 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClusterStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClusterStatement.java new file mode 100644 index 00000000000..ef7a283256a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropClusterStatement.java @@ -0,0 +1,29 @@ +/* Generated By:JJTree: Do not edit this line. ODropClusterStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class ODropClusterStatement extends OStatement { + protected OIdentifier name; + protected OInteger id; + + public ODropClusterStatement(int id) { + super(id); + } + + public ODropClusterStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("DROP CLUSTER "); + if(name!=null){ + name.toString(params, builder); + }else{ + id.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=239ffe92e79e1d5c82976ed9814583ec (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropIndexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropIndexStatement.java new file mode 100644 index 00000000000..0b0c2485882 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropIndexStatement.java @@ -0,0 +1,30 @@ +/* Generated By:JJTree: Do not edit this line. ODropIndexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class ODropIndexStatement extends OStatement { + + protected boolean all = false; + protected OIndexName name; + + public ODropIndexStatement(int id) { + super(id); + } + + public ODropIndexStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("DROP INDEX "); + if (all) { + builder.append("*"); + } else { + name.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=51c8221d049e4f114378e4be03797050 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropPropertyStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropPropertyStatement.java new file mode 100644 index 00000000000..13a952eb577 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropPropertyStatement.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. ODropPropertyStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ODropPropertyStatement extends OStatement { + + protected OIdentifier className; + protected OIdentifier propertyName; + protected boolean ifExists = false; + protected boolean force = false; + + public ODropPropertyStatement(int id) { + super(id); + } + + public ODropPropertyStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("DROP PROPERTY "); + className.toString(params, builder); + builder.append("."); + propertyName.toString(params, builder); + if(ifExists){ + builder.append(" IF EXISTS"); + } + if(force){ + builder.append(" FORCE"); + } + } +} +/* JavaCC - OriginalChecksum=6a9b4b1694dc36caf2b801218faebe42 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropSequenceStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropSequenceStatement.java new file mode 100644 index 00000000000..80975fda4db --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ODropSequenceStatement.java @@ -0,0 +1,24 @@ +/* Generated By:JJTree: Do not edit this line. OAlterSequenceStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class ODropSequenceStatement extends OStatement { + OIdentifier name; + + public ODropSequenceStatement(int id) { + super(id); + } + + public ODropSequenceStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("DROP SEQUENCE "); + name.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=def62b1d04db5223307fe51873a9edd0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OEqualsCompareOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OEqualsCompareOperator.java new file mode 100644 index 00000000000..ed98e395726 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OEqualsCompareOperator.java @@ -0,0 +1,37 @@ +/* Generated By:JJTree: Do not edit this line. OEqualsCompareOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals; + +public class OEqualsCompareOperator extends SimpleNode implements OBinaryCompareOperator { + boolean doubleEquals = false; + + public OEqualsCompareOperator(int id) { + super(id); + } + + public OEqualsCompareOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + return OQueryOperatorEquals.equals(iLeft, iRight); + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + @Override + public String toString() { + return doubleEquals ? "==" : "="; + } +} +/* JavaCC - OriginalChecksum=bd2ec5d13a1d171779c2bdbc9d3a56bc (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExplainStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExplainStatement.java new file mode 100644 index 00000000000..db19318f719 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExplainStatement.java @@ -0,0 +1,25 @@ +/* Generated By:JJTree: Do not edit this line. OExplainStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OExplainStatement extends OStatement { + + protected OStatement statement; + + public OExplainStatement(int id) { + super(id); + } + + public OExplainStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("EXPLAIN "); + statement.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=9fdd24510993cbee32e38a51c838bdb4 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExpression.java new file mode 100644 index 00000000000..9c1123b85ae --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OExpression.java @@ -0,0 +1,196 @@ +/* Generated By:JJTree: Do not edit this line. OExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.id.ORecordId; + +import java.util.List; +import java.util.Map; + +public class OExpression extends SimpleNode { + + protected Boolean singleQuotes; + protected Boolean doubleQuotes; + + public OExpression(int id) { + super(id); + } + + public OExpression(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (value instanceof ORid) { + ORid v = (ORid) value; + return new ORecordId(v.cluster.getValue().intValue(), v.position.getValue().longValue()); + } else if (value instanceof OMathExpression) { + return ((OMathExpression) value).execute(iCurrentRecord, ctx); + } else if (value instanceof OJson) { + return ((OJson) value).toMap(iCurrentRecord, ctx); + } else if (value instanceof String) { + return value; + } else if (value instanceof Number) { + return value; + } + + return value; + + } + + public boolean isBaseIdentifier() { + if (value instanceof OMathExpression) { + return ((OMathExpression) value).isBaseIdentifier(); + } + + return false; + } + + public boolean isEarlyCalculated() { + if (value instanceof Number) { + return true; + } + if (value instanceof String) { + return true; + } + if (value instanceof OMathExpression) { + return ((OMathExpression) value).isEarlyCalculated(); + } + + return false; + } + + public OIdentifier getDefaultAlias() { + + if (value instanceof String) { + OIdentifier identifier = new OIdentifier(-1); + identifier.setValue((String) value); + return identifier; + } + // TODO create an interface for this; + + // if (value instanceof ORid) { + // return null;// TODO + // } else if (value instanceof OMathExpression) { + // return null;// TODO + // } else if (value instanceof OJson) { + // return null;// TODO + // } + + if (value instanceof OBaseExpression && ((OBaseExpression) value).isBaseIdentifier()) { + return ((OBaseExpression) value).identifier.suffix.identifier; + } + + String result = ("" + value).replaceAll("\\.", "_").replaceAll(" ", "_").replaceAll("\n", "_").replaceAll("\b", "_") + .replaceAll("\\[", "_").replaceAll("\\]", "_").replaceAll("\\(", "_").replaceAll("\\)", "_"); + OIdentifier identifier = new OIdentifier(-1); + identifier.setValue(result); + return identifier; + } + + public void toString(Map params, StringBuilder builder) { + if (value == null) { + builder.append("null"); + } else if (value instanceof SimpleNode) { + ((SimpleNode) value).toString(params, builder); + } else if (value instanceof String) { + if (Boolean.TRUE.equals(singleQuotes)) { + builder.append("'" + encode((String)value) + "'"); + } else { + builder.append("\"" + encode((String)value) + "\""); + } + } else { + builder.append("" + value); + } + } + + public static String encode(String s) { + StringBuilder builder = new StringBuilder(s.length()); + for (char c : s.toCharArray()) { + if (c == '\n') { + builder.append("\\n"); + continue; + } + if (c == '\t') { + builder.append("\\t"); + continue; + } + if (c == '\\' || c == '"'|| c == '\'') { + builder.append("\\"); + } + builder.append(c); + } + return builder.toString(); + } + + public boolean supportsBasicCalculation() { + if (value instanceof OMathExpression) { + return ((OMathExpression) value).supportsBasicCalculation(); + } + return true; + } + + public boolean isIndexedFunctionCal() { + if (value instanceof OMathExpression) { + return ((OMathExpression) value).isIndexedFunctionCall(); + } + return false; + } + + public static String encodeSingle(String s) { + + StringBuilder builder = new StringBuilder(s.length()); + for (char c : s.toCharArray()) { + if (c == '\n') { + builder.append("\\n"); + continue; + } + if (c == '\t') { + builder.append("\\t"); + continue; + } + if (c == '\\' || c == '\'') { + builder.append("\\"); + } + builder.append(c); + } + return builder.toString(); + } + + public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) { + if (value instanceof OMathExpression) { + return ((OMathExpression) value).estimateIndexedFunction(target, context, operator, right); + } + return -1; + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context, + OBinaryCompareOperator operator, Object right) { + if (value instanceof OMathExpression) { + return ((OMathExpression) value).executeIndexedFunction(target, context, operator, right); + } + return null; + } + + /** + * if the condition involved the current pattern (MATCH statement, eg. $matched.something = foo), + * returns the name of involved pattern aliases ("something" in this case) + * + * @return a list of pattern aliases involved in this condition. Null it does not involve the pattern + */ + List getMatchPatternInvolvedAliases() { + if (value instanceof OMathExpression) + return ((OMathExpression)value).getMatchPatternInvolvedAliases(); + return null; + } +} +/* JavaCC - OriginalChecksum=9c860224b121acdc89522ae97010be01 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlan.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlan.java new file mode 100644 index 00000000000..992e4bae079 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlan.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OFetchPlan.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OFetchPlan extends SimpleNode { + + protected List items = new ArrayList(); + + public OFetchPlan(int id) { + super(id); + } + + public OFetchPlan(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("FETCHPLAN "); + boolean first = true; + for (OFetchPlanItem item : items) { + if (!first) { + builder.append(" "); + } + + item.toString(params, builder); + first = false; + } + } +} +/* JavaCC - OriginalChecksum=b4cd86f2c6e8fc5e9dce8912389a1167 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlanItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlanItem.java new file mode 100644 index 00000000000..6f2049f18d8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFetchPlanItem.java @@ -0,0 +1,59 @@ +/* Generated By:JJTree: Do not edit this line. OFetchPlanItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OFetchPlanItem extends SimpleNode { + + protected Boolean star; + + protected OInteger leftDepth; + protected boolean leftStar = false; + + protected OInteger rightDepth; + + protected List fieldChain = new ArrayList(); + + public OFetchPlanItem(int id) { + super(id); + } + + public OFetchPlanItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (Boolean.TRUE.equals(star)) { + builder.append("*"); + } else { + if (leftDepth != null) { + builder.append("["); + leftDepth.toString(params, builder); + builder.append("]"); + }else if(leftStar){ + builder.append("[*]"); + } + + boolean first = true; + for (String s : fieldChain) { + if (!first) { + builder.append("."); + } + builder.append(s); + first = false; + } + + } + builder.append(":"); + rightDepth.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=b7f4c9a97a8f2ca3d85020e054a9ad16 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFindReferencesStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFindReferencesStatement.java new file mode 100644 index 00000000000..a87d12c0542 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFindReferencesStatement.java @@ -0,0 +1,49 @@ +/* Generated By:JJTree: Do not edit this line. OFindReferencesStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OFindReferencesStatement extends OStatement { + protected ORid rid; + protected OStatement subQuery; + + //class or cluster + protected List targets; + + public OFindReferencesStatement(int id) { + super(id); + } + + public OFindReferencesStatement(OrientSql p, int id) { + super(p, id); + + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("FIND REFERENCES "); + if (rid != null) { + rid.toString(params, builder); + } else { + builder.append(" ( "); + subQuery.toString(params, builder); + builder.append(" )"); + } + if (targets != null) { + builder.append(" ["); + boolean first = true; + for (SimpleNode node : targets) { + if (!first) { + builder.append(","); + } + node.toString(params, builder); + first = false; + } + builder.append("]"); + } + } + +} +/* JavaCC - OriginalChecksum=be781e05acef94aa5edd7438b4ead6d5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFirstLevelExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFirstLevelExpression.java new file mode 100644 index 00000000000..9697a9231bf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFirstLevelExpression.java @@ -0,0 +1,31 @@ +/* Generated By:JJTree: Do not edit this line. OFirstLevelExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OFirstLevelExpression extends OMathExpression { + public OFirstLevelExpression(int id) { + super(id); + } + + public OFirstLevelExpression(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + protected boolean supportsBasicCalculation() { + return super.supportsBasicCalculation(); + } + + public boolean isBaseIdentifier() { + if (value instanceof OIdentifier) { + return true; + } + return false; + } +} +/* JavaCC - OriginalChecksum=30dc1016b686d4841bbd57d6e6c0bfbd (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFloatingPoint.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFloatingPoint.java new file mode 100644 index 00000000000..e4a2aa99383 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFloatingPoint.java @@ -0,0 +1,48 @@ +/* Generated By:JJTree: Do not edit this line. OFloatingPoint.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OFloatingPoint extends ONumber { + + protected int sign = 1; + protected String stringValue = null; + + public OFloatingPoint(int id) { + super(id); + } + + public OFloatingPoint(OrientSql p, int id) { + super(p, id); + } + + @Override + public Number getValue() { + return Double.parseDouble((sign == -1 ? "-" : "") + stringValue); + } + + public int getSign() { + return sign; + } + + public void setSign(int sign) { + this.sign = sign; + } + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + public void toString(Map params, StringBuilder builder) { + if (sign == -1) { + builder.append("-"); + } + builder.append(stringValue); + } +} +/* JavaCC - OriginalChecksum=46acfb589f666717595e28f1b19611ae (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromClause.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromClause.java new file mode 100644 index 00000000000..2328df05c6e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromClause.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OFromClause.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OFromClause extends SimpleNode { + + OFromItem item; + + public OFromClause(int id) { + super(id); + } + + public OFromClause(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (item != null) { + item.toString(params, builder); + } + } + + + public OFromItem getItem() { + return item; + } +} +/* JavaCC - OriginalChecksum=051839d20dabfa4cce26ebcbe0d03a86 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromItem.java new file mode 100644 index 00000000000..2bfd8d6fe7c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFromItem.java @@ -0,0 +1,91 @@ +/* Generated By:JJTree: Do not edit this line. OFromItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OFromItem extends SimpleNode { + + protected List rids; + protected OCluster cluster; + protected OClusterList clusterList; + // protected OIdentifier className; + protected OIndexIdentifier index; + protected OMetadataIdentifier metadata; + protected OStatement statement; + protected OInputParameter inputParam; + protected OBaseIdentifier identifier; + protected OModifier modifier; + + private static final Object UNSET = new Object(); + private Object inputFinalValue = UNSET; + + public OFromItem(int id) { + super(id); + } + + public OFromItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (rids != null && rids.size() > 0) { + if (rids.size() == 1) { + rids.get(0).toString(params, builder); + return; + } else { + builder.append("["); + boolean first = true; + for (ORid rid : rids) { + if (!first) { + builder.append(", "); + } + rid.toString(params, builder); + first = false; + } + builder.append("]"); + return; + } + } else if (cluster != null) { + cluster.toString(params, builder); + return; + // } else if (className != null) { + // return className.getValue(); + } else if (clusterList != null) { + clusterList.toString(params, builder); + return; + } else if (metadata != null) { + metadata.toString(params, builder); + return; + } else if (statement != null) { + builder.append("("); + statement.toString(params, builder); + builder.append(")"); + return; + } else if (index != null) { + index.toString(params, builder); + return; + } else if (inputParam != null) { + inputParam.toString(params, builder); + } else if (identifier != null) { + + identifier.toString(params, builder); + if (modifier != null) { + modifier.toString(params, builder); + } + return; + } + } + + + public OBaseIdentifier getIdentifier() { + return identifier; + } +} +/* JavaCC - OriginalChecksum=f64e3b4d2a2627a1b5d04a7dcb95fa94 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFunctionCall.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFunctionCall.java new file mode 100644 index 00000000000..c0777c94327 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OFunctionCall.java @@ -0,0 +1,141 @@ +/* Generated By:JJTree: Do not edit this line. OFunctionCall.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.functions.OIndexableSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OFunctionCall extends SimpleNode { + + protected OIdentifier name; + protected boolean star = false; + protected List params = new ArrayList(); + + public OFunctionCall(int id) { + super(id); + } + + public OFunctionCall(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean isStar() { + return star; + } + + public void setStar(boolean star) { + this.star = star; + } + + public List getParams() { + return params; + } + + public void setParams(List params) { + this.params = params; + } + + public void toString(Map params, StringBuilder builder) { + name.toString(params, builder); + builder.append("("); + if (star) { + builder.append("*"); + } else { + boolean first = true; + for (OExpression expr : this.params) { + if (!first) { + builder.append(", "); + } + expr.toString(params, builder); + first = false; + } + } + builder.append(")"); + } + + public Object execute(Object targetObjects, OCommandContext ctx) { + return execute(targetObjects, ctx, name.getStringValue()); + } + + private Object execute(Object targetObjects, OCommandContext ctx, String name) { + List paramValues = new ArrayList(); + OIdentifiable record = ctx == null ? null : (OIdentifiable) ctx.getVariable("$current"); + if (record == null && targetObjects instanceof OIdentifiable) { + record = (OIdentifiable) targetObjects; + } + for (OExpression expr : this.params) { + paramValues.add(expr.execute(record, ctx)); + } + OSQLFunction function = OSQLEngine.getInstance().getFunction(name); + if (function != null) { + return function.execute(targetObjects, record, null, paramValues.toArray(), ctx); + } + throw new UnsupportedOperationException("This expression is not currently supported: "+toString()); + } + + public static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + + public boolean isIndexedFunctionCall() { + OSQLFunction function = OSQLEngine.getInstance().getFunction(name.getStringValue()); + return (function instanceof OIndexableSQLFunction); + } + + /** + * see OIndexableSQLFunction.searchFromTarget() + * + * @param target + * @param ctx + * @param operator + * @param rightValue + * @return + */ + public Iterable executeIndexedFunction(OFromClause target, OCommandContext ctx, OBinaryCompareOperator operator, + Object rightValue) { + OSQLFunction function = OSQLEngine.getInstance().getFunction(name.getStringValue()); + if (function instanceof OIndexableSQLFunction) { + return ((OIndexableSQLFunction) function).searchFromTarget(target, operator, rightValue, ctx, + this.getParams().toArray(new OExpression[] {})); + } + return null; + } + + /** + * + * @param target + * query target + * @param ctx + * execution context + * @param operator + * operator at the right of the function + * @param rightValue + * value to compare to funciton result + * @return the approximate number of items returned by the condition execution, -1 if the extimation cannot be executed + */ + public long estimateIndexedFunction(OFromClause target, OCommandContext ctx, OBinaryCompareOperator operator, Object rightValue) { + OSQLFunction function = OSQLEngine.getInstance().getFunction(name.getStringValue()); + if (function instanceof OIndexableSQLFunction) { + return ((OIndexableSQLFunction) function).estimate(target, operator, rightValue, ctx, + this.getParams().toArray(new OExpression[] {})); + } + return -1; + } +} +/* JavaCC - OriginalChecksum=290d4e1a3f663299452e05f8db718419 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGeOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGeOperator.java new file mode 100644 index 00000000000..18f5950490f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGeOperator.java @@ -0,0 +1,70 @@ +/* Generated By:JJTree: Do not edit this line. OGeOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +/* + * + * * Copyright 2015 Orient Technologies LTD (info(at)orientdb.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientdb.com + * + */ + +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +public class OGeOperator extends SimpleNode implements OBinaryCompareOperator { + public OGeOperator(int id) { + super(id); + } + + public OGeOperator(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean execute(Object iLeft, Object iRight) { + if (iLeft == null || iRight == null) { + return false;//only one is null, to check if both are null please use IS NULL + } + + if (iLeft.getClass() != iRight.getClass() && iLeft instanceof Number && iRight instanceof Number) { + Number[] couple = OType.castComparableNumber((Number) iLeft, (Number) iRight); + iLeft = couple[0]; + iRight = couple[1]; + } else { + iRight = OType.convert(iRight, iLeft.getClass()); + } + if (iRight == null) + return false; + return ((Comparable) iLeft).compareTo(iRight) >= 0; + } + + @Override public String toString() { + return ">="; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + +} +/* JavaCC - OriginalChecksum=960da239569d393eb155f7d8a871e6d5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGrantStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGrantStatement.java new file mode 100644 index 00000000000..0d4db51ff25 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGrantStatement.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OGrantStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OGrantStatement extends OStatement { + protected OPermission permission; + protected List resourceChain = new ArrayList(); + protected OIdentifier actor; + + public OGrantStatement(int id) { + super(id); + } + + public OGrantStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("GRANT "); + permission.toString(params, builder); + builder.append(" ON "); + boolean first = true; + for (OResourcePathItem res : resourceChain) { + if (!first) { + builder.append("."); + } + res.toString(params, builder); + first = false; + } + builder.append(" TO "); + actor.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=c5f7b91e57070a95c6ea490373d16f7f (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGroupBy.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGroupBy.java new file mode 100644 index 00000000000..9592a703017 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGroupBy.java @@ -0,0 +1,38 @@ +/* Generated By:JJTree: Do not edit this line. OGroupBy.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OGroupBy extends SimpleNode { + + protected List items = new ArrayList(); + + public OGroupBy(int id) { + super(id); + } + + public OGroupBy(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("GROUP BY "); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + builder.append(", "); + } + items.get(i).toString(params, builder); + } + } + + +} +/* JavaCC - OriginalChecksum=4739190aa6c1a3533a89b76a15bd6fdf (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGtOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGtOperator.java new file mode 100644 index 00000000000..ec9c2777621 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OGtOperator.java @@ -0,0 +1,50 @@ +/* Generated By:JJTree: Do not edit this line. OGtOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +public class OGtOperator extends SimpleNode implements OBinaryCompareOperator { + public OGtOperator(int id) { + super(id); + } + + public OGtOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + if (iLeft == null || iRight == null) { + return false;//only one is null, to check if both are null please use IS NULL + } + + if (iLeft.getClass() != iRight.getClass() && iLeft instanceof Number && iRight instanceof Number) { + Number[] couple = OType.castComparableNumber((Number) iLeft, (Number) iRight); + iLeft = couple[0]; + iRight = couple[1]; + } else { + iRight = OType.convert(iRight, iLeft.getClass()); + } + if (iRight == null) + return false; + return ((Comparable) iLeft).compareTo(iRight) > 0; + } + + @Override + public String toString() { + return ">"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=4b96739fc6e9ae496916d542db361376 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaRemoveServerStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaRemoveServerStatement.java new file mode 100644 index 00000000000..b2b32d0fe9f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaRemoveServerStatement.java @@ -0,0 +1,31 @@ +/* Generated By:JJTree: Do not edit this line. OHaRemoveServerStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OHaRemoveServerStatement extends OStatement { + + public OIdentifier serverName; + + public OHaRemoveServerStatement(int id) { + super(id); + } + + public OHaRemoveServerStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("HA REMOVE SERVER "); + serverName.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=9c136e8917527d69a67c88582d20ac8f (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaStatusStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaStatusStatement.java new file mode 100644 index 00000000000..bf41bc6019e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaStatusStatement.java @@ -0,0 +1,53 @@ +/* Generated By:JJTree: Do not edit this line. OHaStatusStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OHaStatusStatement extends OStatement { + + public boolean servers = false; + public boolean db = false; + public boolean latency = false; + public boolean messages = false; + public boolean outputText = false; + + public OHaStatusStatement(int id) { + super(id); + } + + public OHaStatusStatement(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("HA STATUS"); + if (servers) { + builder.append(" -servers"); + } + if (db) { + builder.append(" -db"); + } + if (latency) { + builder.append(" -latency"); + } + if (messages) { + builder.append(" -messages"); + } + if (outputText) { + builder.append(" -output=text"); + } + if (servers) { + builder.append(" -servers"); + } + } + +} +/* JavaCC - OriginalChecksum=c8ab1b0172e8cdbea2078efe2c629e6a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncClusterStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncClusterStatement.java new file mode 100644 index 00000000000..36b7f02d3b6 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncClusterStatement.java @@ -0,0 +1,33 @@ +/* Generated By:JJTree: Do not edit this line. OHaSyncClusterStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OHaSyncClusterStatement extends OStatement { + + public OIdentifier clusterName; + public boolean modeFull = true; + public boolean modeMerge = false; + + public OHaSyncClusterStatement(int id) { + super(id); + } + + public OHaSyncClusterStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("HA SYNC CLUSTER "); + clusterName.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=fbf0df8004d889ebc80f39be85008720 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncDatabaseStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncDatabaseStatement.java new file mode 100644 index 00000000000..4c3db7577d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OHaSyncDatabaseStatement.java @@ -0,0 +1,46 @@ +/* Generated By:JJTree: Do not edit this line. OHaSyncDatabaseStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OHaSyncDatabaseStatement extends OStatement { + + public boolean force = false; + public boolean full = false; + + public OHaSyncDatabaseStatement(int id) { + super(id); + } + + public OHaSyncDatabaseStatement(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("HA SYNC DATABASE"); + if(force){ + builder.append(" -force"); + } + if(full){ + builder.append(" -full"); + } + } + + public boolean isForce() { + return force; + } + + public boolean isFull() { + return full; + } +} +/* JavaCC - OriginalChecksum=f2c9070be78798e3093a98669129ce0d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIdentifier.java new file mode 100644 index 00000000000..454185f7632 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIdentifier.java @@ -0,0 +1,86 @@ +/* Generated By:JJTree: Do not edit this line. OIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OIdentifier extends SimpleNode { + + protected String value; + protected boolean quoted = false; + + public OIdentifier(int id) { + super(id); + } + + public OIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + /** + * returns the value as is, with back-ticks quoted with backslash + * @return + */ + public String getValue() { + return value; + } + + /** + * accepts a plain value. Back-ticks have to be quoted. + * @param value + */ + public void setValue(String value) { + this.value = value; + } + + /** + * returns the plain string representation of this identifier, with quoting removed from back-ticks + * @return + */ + public String getStringValue(){ + if(value == null){ + return null; + } + return value.replaceAll("\\\\`", "`"); + } + + /** + * returns the plain string representation of this identifier, with quoting removed from back-ticks + * @return + */ + public void setStringValue(String s){ + if(s == null){ + value = null; + }else{ + value = s.replaceAll("`", "\\\\`"); + } + } + + + @Override + public String toString(String prefix) { + if (quoted) { + return '`' + value + '`'; + } + return value; + } + + public String toString(){ + return toString(""); + } + + public void toString(Map params, StringBuilder builder) { + if (quoted) { + builder.append('`' + value + '`'); + } else { + builder.append(value); + } + } + +} +/* JavaCC - OriginalChecksum=691a2eb5096f7b5e634b2ca8ac2ded3a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfNotExists.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfNotExists.java new file mode 100644 index 00000000000..f7b1a61973f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfNotExists.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OIfNotExists.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OIfNotExists extends SimpleNode { + public OIfNotExists(int id) { + super(id); + } + + public OIfNotExists(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=5990db905ac7259f864fa5c62f123bcc (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfStatement.java new file mode 100644 index 00000000000..cde4cc7cc75 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIfStatement.java @@ -0,0 +1,32 @@ +/* Generated By:JJTree: Do not edit this line. OIfStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OIfStatement extends OStatement { + protected OBooleanExpression expression; + protected List statements = new ArrayList(); + + public OIfStatement(int id) { + super(id); + } + + public OIfStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("IF("); + expression.toString(params, builder); + builder.append("){\n"); + for (OStatement stm : statements) { + stm.toString(params, builder); + builder.append(";\n"); + } + builder.append("}"); + } +} +/* JavaCC - OriginalChecksum=a8cd4fb832a4f3b6e71bb1a12f8d8819 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInCondition.java new file mode 100644 index 00000000000..3a85d3c8f88 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInCondition.java @@ -0,0 +1,167 @@ +/* Generated By:JJTree: Do not edit this line. OInCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.filter.OSQLTarget; + +import java.util.*; + +public class OInCondition extends OBooleanExpression { + protected OExpression left; + protected OBinaryCompareOperator operator; + protected OSelectStatement rightStatement; + protected OInputParameter rightParam; + protected OMathExpression rightMathExpression; + protected Object right; + + private static final Object UNSET = new Object(); + private Object inputFinalValue = UNSET; + + public OInCondition(int id) { + super(id); + } + + public OInCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + Object leftValue = left.execute(currentRecord, ctx); + Object rightValue = null; + if (rightStatement != null) { + rightValue = query(rightStatement.toString(), ctx); + } else if (rightParam != null) { + rightValue = rightParam.bindFromInputParams(ctx.getInputParameters()); + } else if (rightMathExpression != null) { + rightValue = rightMathExpression.execute(currentRecord, ctx); + } else { + rightValue = right; + } + + if (rightValue == null) { + return false; + } else if (rightValue instanceof Collection) { + return ((Collection) rightValue).contains(leftValue); + } else if (rightValue instanceof OIdentifiable) { + return rightValue.equals(leftValue); + } + if (rightValue instanceof Iterable) { + rightValue = ((Iterable) rightValue).iterator(); + } + if (rightValue instanceof Iterator) { + Iterator iter = ((Iterator) rightValue); + while (iter.hasNext()) { + Object next = iter.next(); + if (next == null && leftValue == null) { + return true; + } + if (next != null && next.equals(leftValue)) { + return true; + } + } + } + return false; + } + + private Object query(String text, OCommandContext ctx) { + OSQLTarget target = new OSQLTarget(text, ctx); + Iterable targetResult = (Iterable) target.getTargetRecords(); + if (targetResult == null) { + return null; + } + return targetResult.iterator(); + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" IN "); + if (rightStatement != null) { + builder.append("("); + rightStatement.toString(params, builder); + builder.append(")"); + } else if (right != null) { + builder.append(convertToString(right)); + } else if (rightParam != null) { + rightParam.toString(params, builder); + } else if (rightMathExpression != null) { + rightMathExpression.toString(params, builder); + } + } + + private String convertToString(Object o) { + if (o instanceof String) { + return "\"" + ((String) o).replaceAll("\"", "\\\"") + "\""; + } + return o.toString(); + } + + @Override public boolean supportsBasicCalculation() { + if (!left.supportsBasicCalculation()) { + return false; + } + if (!rightMathExpression.supportsBasicCalculation()) { + return false; + } + if (!operator.supportsBasicCalculation()) { + return false; + } + + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + int total = 0; + if (operator != null && !operator.supportsBasicCalculation()) { + total++; + } + if (!left.supportsBasicCalculation()) { + total++; + } + if (rightMathExpression != null && !rightMathExpression.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + + if (operator != null) { + result.add(this); + } + if (!left.supportsBasicCalculation()) { + result.add(left); + } + if (rightMathExpression != null && !rightMathExpression.supportsBasicCalculation()) { + result.add(rightMathExpression); + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + + List conditionX = rightMathExpression == null ? null : rightMathExpression.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (conditionX != null) { + result.addAll(conditionX); + } + + return result.size() == 0 ? null : result; + } + +} +/* JavaCC - OriginalChecksum=00df7cb1877c0a12d24205c1700653c7 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInOperator.java new file mode 100644 index 00000000000..17fc7d0071a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInOperator.java @@ -0,0 +1,79 @@ +/* Generated By:JJTree: Do not edit this line. OInOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Collection; +import java.util.Iterator; + +public class OInOperator extends SimpleNode implements OBinaryCompareOperator { + public OInOperator(int id) { + super(id); + } + + public OInOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + if (left == null) { + return false; + } + if (right instanceof Collection) { + if (left instanceof Collection) { + return ((Collection) right).containsAll((Collection) left); + } + if (left instanceof Iterable) { + left = ((Iterable) left).iterator(); + } + if (left instanceof Iterator) { + Iterator iterator = (Iterator) left; + while (iterator.hasNext()) { + Object next = iterator.next(); + if (!((Collection) right).contains(next)) { + return false; + } + } + } + return ((Collection) right).contains(left); + } + if (right instanceof Iterable) { + right = ((Iterable) right).iterator(); + } + if (right instanceof Iterator) { + if (left instanceof Iterable) { + left = ((Iterable) left).iterator(); + } + Iterator leftIterator = (Iterator) left; + Iterator rightIterator = (Iterator) right; + while (leftIterator.hasNext()) { + Object leftItem = leftIterator.next(); + boolean found = false; + while (rightIterator.hasNext()) { + Object rightItem = rightIterator.next(); + if (leftItem != null && leftItem.equals(rightItem)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + return false; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=6650a720cb942fa3c4d588ff0f381b3a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItem.java new file mode 100644 index 00000000000..2ed12347f5a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItem.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OInPathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OInPathItem extends OMatchPathItem { + public OInPathItem(int id) { + super(id); + } + + public OInPathItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("<-"); + boolean first = true; + if (this.method.params != null) { + for (OExpression exp : this.method.params) { + if (!first) { + builder.append(", "); + } + builder.append(exp.execute(null, null)); + first = false; + } + } + builder.append("-"); + if (filter != null) { + filter.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=a1d80718c0b913e46b7b6a1c38e0dc98 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItemOpt.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItemOpt.java new file mode 100644 index 00000000000..c0c2660177a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInPathItemOpt.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. OInPathItemOpt.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OInPathItemOpt extends OInPathItem { + public OInPathItemOpt(int id) { + super(id); + } + + public OInPathItemOpt(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=ef282589054869578c47f554474b5c3b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexIdentifier.java new file mode 100644 index 00000000000..0a310d127cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexIdentifier.java @@ -0,0 +1,53 @@ +/* Generated By:JJTree: Do not edit this line. OIndexIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OIndexIdentifier extends SimpleNode { + + public enum Type { + INDEX, VALUES, VALUESASC, VALUESDESC + } + + protected Type type; + protected String indexNameString; + protected OIndexName indexName; + + public OIndexIdentifier(int id) { + super(id); + } + + public OIndexIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + switch (type) { + case INDEX: + builder.append("INDEX"); + break; + case VALUES: + builder.append("INDEXVALUES"); + break; + case VALUESASC: + builder.append("INDEXVALUESASC"); + break; + case VALUESDESC: + builder.append("INDEXVALUESDESC"); + break; + } + builder.append(":"); + if(indexNameString!=null) { + builder.append(indexNameString); + }else{ + indexName.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=025f134fd4b27b84210738cdb6dd027c (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexMatchCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexMatchCondition.java new file mode 100644 index 00000000000..0610151b268 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexMatchCondition.java @@ -0,0 +1,96 @@ +/* Generated By:JJTree: Do not edit this line. OIndexMatchCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OIndexMatchCondition extends OBooleanExpression{ + + protected OBinaryCompareOperator operator; + protected Boolean between; + + protected List leftExpressions; + protected List rightExpressions; + + public OIndexMatchCondition(int id) { + super(id); + } + + public OIndexMatchCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + + public void toString(Map params, StringBuilder builder) { + builder.append("KEY "); + if (operator != null) { + builder.append(operator.toString()); + builder.append(" ["); + boolean first = true; + for (OExpression x : leftExpressions) { + if (!first) { + builder.append(", "); + } + x.toString(params, builder); + first = false; + } + builder.append("]"); + } else if (Boolean.TRUE.equals(between)) { + builder.append(" BETWEEN ["); + boolean first = true; + for (OExpression x : leftExpressions) { + if (!first) { + builder.append(", "); + } + x.toString(params, builder); + first = false; + } + builder.append("] AND ["); + first = true; + for (OExpression x : rightExpressions) { + if (!first) { + builder.append(", "); + } + x.toString(params, builder); + first = false; + } + builder.append("]"); + } + } + + @Override public boolean supportsBasicCalculation() { + return false; + } + + @Override + protected int getNumberOfExternalCalculations() { + return 1; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + result.add(this); + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + return null; + } + +} +/* JavaCC - OriginalChecksum=702e9ab959e87b043b519844a7d31224 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexName.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexName.java new file mode 100644 index 00000000000..a43cb9f6b53 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIndexName.java @@ -0,0 +1,25 @@ +/* Generated By:JJTree: Do not edit this line. OIndexName.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OIndexName extends SimpleNode { + public OIndexName(int id) { + super(id); + } + + public OIndexName(OrientSql p, int id) { + super(p, id); + } + + public String getValue() { + return value.toString(); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append(getValue()); + } +} +/* JavaCC - OriginalChecksum=06c827926e7e9ee650b76d42e31feb46 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInputParameter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInputParameter.java new file mode 100644 index 00000000000..f72be013dad --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInputParameter.java @@ -0,0 +1,143 @@ +/* Generated By:JJTree: Do not edit this line. OInputParameter.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.serialization.OBase64Utils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; + +public class OInputParameter extends SimpleNode { + + protected String dateFormatString = "yyyy-MM-dd HH:mm:ss.SSS"; + protected DateFormat dateFormat = new SimpleDateFormat(dateFormatString); + + public OInputParameter(int id) { + super(id); + } + + public OInputParameter(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public Object bindFromInputParams(Map params) { + return null; + } + + protected Object toParsedTree(Object value) { + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return value; + } + if (value instanceof Integer) { + OInteger result = new OInteger(-1); + result.setValue((Integer) value); + return result; + } + if (value instanceof Number) { + OFloatingPoint result = new OFloatingPoint(-1); + result.sign = ((Number) value).doubleValue() >= 0 ? 1 : -1; + result.stringValue = value.toString(); + if (result.stringValue.startsWith("-")) { + result.stringValue = result.stringValue.substring(1); + } + return result; + } + if (value instanceof String) { + return value; + } + if (value instanceof Map) { + OJson json = new OJson(-1); + json.items = new ArrayList(); + for (Object entry : ((Map) value).entrySet()) { + OJsonItem item = new OJsonItem(); + item.leftString = "" + ((Map.Entry) entry).getKey(); + OExpression exp = new OExpression(-1); + exp.value = toParsedTree(((Map.Entry) entry).getValue()); + item.right = exp; + json.items.add(item); + } + return json; + } + if (OMultiValue.isMultiValue(value) && !(value instanceof byte[]) && !(value instanceof Byte[])) { + OCollection coll = new OCollection(-1); + coll.expressions = new ArrayList(); + Iterator iterator = OMultiValue.getMultiValueIterator(value); + while (iterator.hasNext()) { + Object o = iterator.next(); + OExpression exp = new OExpression(-1); + exp.value = toParsedTree(o); + coll.expressions.add(exp); + } + return coll; + } + if (value instanceof OIdentifiable) { + // TODO if invalid build a JSON + ORid rid = new ORid(-1); + String stringVal = ((OIdentifiable) value).getIdentity().toString().substring(1); + String[] splitted = stringVal.split(":"); + OInteger c = new OInteger(-1); + c.setValue(Integer.parseInt(splitted[0])); + rid.cluster = c; + OInteger p = new OInteger(-1); + p.setValue(Integer.parseInt(splitted[1])); + rid.position = p; + return rid; + } + if (value instanceof Date) { + OFunctionCall function = new OFunctionCall(-1); + function.name = new OIdentifier(-1); + function.name.value = "date"; + + OExpression dateExpr = new OExpression(-1); + dateExpr.singleQuotes = true; + dateExpr.doubleQuotes = false; + dateExpr.value = dateFormat.format(value); + function.getParams().add(dateExpr); + + OExpression dateFormatExpr = new OExpression(-1); + dateFormatExpr.singleQuotes = true; + dateFormatExpr.doubleQuotes = false; + dateFormatExpr.value = dateFormatString; + function.getParams().add(dateFormatExpr); + return function; + } + if (value instanceof byte[]) { + OFunctionCall function = new OFunctionCall(-1); + function.name = new OIdentifier(-1); + function.name.value = "decode"; + + OExpression valueExpr = new OExpression(-1); + valueExpr.singleQuotes = true; + valueExpr.doubleQuotes = false; + valueExpr.value = OBase64Utils.encodeBytes((byte[]) value); + function.getParams().add(valueExpr); + + OExpression dateFormatExpr = new OExpression(-1); + dateFormatExpr.singleQuotes = true; + dateFormatExpr.doubleQuotes = false; + dateFormatExpr.value = "base64"; + function.getParams().add(dateFormatExpr); + return function; + } + + return this; + } + +} +/* JavaCC - OriginalChecksum=bb2f3732f5e3be4d954527ee0baa9020 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertBody.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertBody.java new file mode 100644 index 00000000000..12479ead56d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertBody.java @@ -0,0 +1,105 @@ +/* Generated By:JJTree: Do not edit this line. OInsertBody.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OInsertBody extends SimpleNode { + + protected List identifierList; + protected List> valueExpressions; + protected List setExpressions; + + protected OSelectStatement selectStatement; + protected boolean selectInParentheses; + protected OJson content; + + protected OProjection returnProjection; + + public OInsertBody(int id) { + super(id); + } + + public OInsertBody(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + + + if (identifierList != null) { + builder.append("("); + boolean first = true; + for (OIdentifier item : identifierList) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + builder.append(") VALUES "); + if (valueExpressions != null) { + boolean firstList = true; + for (List itemList : valueExpressions) { + if (firstList) { + builder.append("("); + } else { + builder.append("),("); + } + first = true; + for (OExpression item : itemList) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + firstList = false; + } + } + builder.append(")"); + + } + + if (setExpressions != null) { + builder.append("SET "); + boolean first = true; + for (OInsertSetExpression item : setExpressions) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + } + + if (selectStatement != null) { + builder.append("FROM "); + if (selectInParentheses) { + builder.append("( "); + } + selectStatement.toString(params, builder); + if (selectInParentheses) { + builder.append(")"); + } + } + + if (content != null) { + builder.append("CONTENT "); + content.toString(params, builder); + } + + if (returnProjection != null) { + builder.append(" RETURN "); + returnProjection.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=7d2079a41a1fc63a812cb679e729b23a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertSetExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertSetExpression.java new file mode 100644 index 00000000000..c2ddabc7653 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertSetExpression.java @@ -0,0 +1,19 @@ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +/** + * Created by luigidellaquila on 19/02/15. + */ +public class OInsertSetExpression { + + protected OIdentifier left; + protected OExpression right; + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" = "); + right.toString(params, builder); + + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertStatement.java new file mode 100644 index 00000000000..0eb268e3ace --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInsertStatement.java @@ -0,0 +1,72 @@ +/* Generated By:JJTree: Do not edit this line. OInsertStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OInsertStatement extends OStatement { + + OIdentifier targetClass; + OIdentifier targetClusterName; + OCluster targetCluster; + OIndexIdentifier targetIndex; + OInsertBody insertBody; + OProjection returnStatement; + OSelectStatement selectStatement; + boolean selectInParentheses = false; + boolean selectWithFrom = false; + boolean unsafe = false; + + public OInsertStatement(int id) { + super(id); + } + + public OInsertStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("INSERT INTO "); + if (targetClass != null) { + targetClass.toString(params, builder); + if (targetClusterName != null) { + builder.append(" CLUSTER "); + targetClusterName.toString(params, builder); + } + } + if (targetCluster != null) { + targetCluster.toString(params, builder); + } + if (targetIndex != null) { + targetIndex.toString(params, builder); + } + if (insertBody != null) { + builder.append(" "); + insertBody.toString(params, builder); + } + if (returnStatement != null) { + builder.append(" RETURN "); + returnStatement.toString(params, builder); + } + if (selectStatement != null) { + builder.append(" "); + if (selectWithFrom) { + builder.append("FROM "); + } + if (selectInParentheses) { + builder.append("("); + } + selectStatement.toString(params, builder); + if (selectInParentheses) { + builder.append(")"); + } + + } + if (unsafe) { + builder.append(" UNSAFE"); + } + } + + +} +/* JavaCC - OriginalChecksum=ccfabcf022d213caed873e6256cb26ad (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInstanceofCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInstanceofCondition.java new file mode 100644 index 00000000000..e13aa9456b3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInstanceofCondition.java @@ -0,0 +1,100 @@ +/* Generated By:JJTree: Do not edit this line. OInstanceofCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OInstanceofCondition extends OBooleanExpression{ + + protected OExpression left; + protected OIdentifier right; + protected String rightString; + + public OInstanceofCondition(int id) { + super(id); + } + + public OInstanceofCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + if (currentRecord == null) { + return false; + } + ORecord record = currentRecord.getRecord(); + if (record == null) { + return false; + } + if (!(record instanceof ODocument)) { + return false; + } + ODocument doc = (ODocument) record; + OClass clazz = doc.getSchemaClass(); + if (clazz == null) { + return false; + } + if (right != null) { + return clazz.isSubClassOf(right.getStringValue()); + } else if (rightString != null) { + return clazz.isSubClassOf(decode(rightString)); + } + return false; + } + + private String decode(String rightString) { + if(rightString==null){ + return null; + } + return OStringSerializerHelper.decode(rightString.substring(1, rightString.length()-1)); + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" instanceof "); + if (right != null) { + right.toString(params, builder); + } else if (rightString != null) { + builder.append(rightString); + } + } + + @Override public boolean supportsBasicCalculation() { + return left.supportsBasicCalculation(); + } + + @Override protected int getNumberOfExternalCalculations() { + if (!left.supportsBasicCalculation()) { + return 1; + } + return 0; + } + + @Override protected List getExternalCalculationConditions() { + if (!left.supportsBasicCalculation()) { + return (List) Collections.singletonList(left); + } + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return left == null ? null : left.getMatchPatternInvolvedAliases(); + } +} +/* JavaCC - OriginalChecksum=0b5eb529744f307228faa6b26f0592dc (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInteger.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInteger.java new file mode 100644 index 00000000000..6777c7d0a17 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OInteger.java @@ -0,0 +1,31 @@ +/* Generated By:JJTree: Do not edit this line. OInteger.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OInteger extends ONumber { + + protected Number value; + + public OInteger(int id) { + super(id); + } + + public OInteger(OrientSql p, int id) { + super(p, id); + } + + public Number getValue() { + return value; + } + + public void setValue(Number value) { + this.value = value; + } + + public void toString(Map params, StringBuilder builder) { + builder.append("" + value); + } +} +/* JavaCC - OriginalChecksum=2e6eee6366ff4e864dd6c8184d2766f5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsDefinedCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsDefinedCondition.java new file mode 100644 index 00000000000..c5264f81209 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsDefinedCondition.java @@ -0,0 +1,58 @@ +/* Generated By:JJTree: Do not edit this line. OIsDefinedCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OIsDefinedCondition extends OBooleanExpression implements OSimpleBooleanExpression { + + protected OExpression expression; + + public OIsDefinedCondition(int id) { + super(id); + } + + public OIsDefinedCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + public void toString(Map params, StringBuilder builder) { + expression.toString(params, builder); + builder.append(" is defined"); + } + + @Override + public boolean supportsBasicCalculation() { + return true; + } + + @Override + protected int getNumberOfExternalCalculations() { + return 0; + } + + @Override + protected List getExternalCalculationConditions() { + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases(); + } +} +/* JavaCC - OriginalChecksum=075954b212c8cb44c8538bf5dea047d3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotDefinedCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotDefinedCondition.java new file mode 100644 index 00000000000..faf52ae1678 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotDefinedCondition.java @@ -0,0 +1,58 @@ +/* Generated By:JJTree: Do not edit this line. OIsNotDefinedCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OIsNotDefinedCondition extends OBooleanExpression { + + protected OExpression expression; + + public OIsNotDefinedCondition(int id) { + super(id); + } + + public OIsNotDefinedCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + + @Override public boolean supportsBasicCalculation() { + return true; + } + + @Override protected int getNumberOfExternalCalculations() { + return 0; + } + + @Override protected List getExternalCalculationConditions() { + return Collections.EMPTY_LIST; + } + + public void toString(Map params, StringBuilder builder) { + expression.toString(params, builder); + builder.append(" is not defined"); + } + + @Override public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases(); + } + + +} +/* JavaCC - OriginalChecksum=1c766d6caf5ccae19c1c291396bb56f2 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotNullCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotNullCondition.java new file mode 100644 index 00000000000..7f682a16307 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNotNullCondition.java @@ -0,0 +1,65 @@ +/* Generated By:JJTree: Do not edit this line. OIsNotNullCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OIsNotNullCondition extends OBooleanExpression { + + protected OExpression expression; + + public OIsNotNullCondition(int id) { + super(id); + } + + public OIsNotNullCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return expression.execute(currentRecord, ctx) != null; + } + + public void toString(Map params, StringBuilder builder) { + expression.toString(params, builder); + builder.append(" IS NOT NULL"); + } + + @Override + public boolean supportsBasicCalculation() { + return expression.supportsBasicCalculation(); + } + + @Override + protected int getNumberOfExternalCalculations() { + if (!expression.supportsBasicCalculation()) { + return 1; + } + return 0; + } + + @Override + protected List getExternalCalculationConditions() { + if (!expression.supportsBasicCalculation()) { + return (List) Collections.singletonList(expression); + } + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases(); + } + +} +/* JavaCC - OriginalChecksum=a292fa8a629abb7f6fe72a627fc91361 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNullCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNullCondition.java new file mode 100644 index 00000000000..54fbd0c1a00 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OIsNullCondition.java @@ -0,0 +1,71 @@ +/* Generated By:JJTree: Do not edit this line. OIsNullCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OIsNullCondition extends OBooleanExpression{ + + protected OExpression expression; + + public OIsNullCondition(int id) { + super(id); + } + + public OIsNullCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return expression.execute(currentRecord, ctx) == null; + } + + public OExpression getExpression() { + return expression; + } + + public void setExpression(OExpression expression) { + this.expression = expression; + } + + public void toString(Map params, StringBuilder builder) { + expression.toString(params, builder); + builder.append(" is null"); + } + + @Override public boolean supportsBasicCalculation() { + return expression.supportsBasicCalculation(); + } + + @Override protected int getNumberOfExternalCalculations() { + if (expression.supportsBasicCalculation()) { + return 0; + } + return 1; + } + + @Override protected List getExternalCalculationConditions() { + if (expression.supportsBasicCalculation()) { + return Collections.EMPTY_LIST; + } + return (List) Collections.singletonList(expression); + } + + @Override public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases(); + } + +} +/* JavaCC - OriginalChecksum=29ebbc506a98f90953af91a66a03aa1e (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJson.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJson.java new file mode 100644 index 00000000000..8d3066524b7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJson.java @@ -0,0 +1,94 @@ +/* Generated By:JJTree: Do not edit this line. OJson.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.*; + +public class OJson extends SimpleNode { + + protected List items = new ArrayList(); + + public OJson(int id) { + super(id); + } + + public OJson(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("{"); + boolean first = true; + for (OJsonItem item : items) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + + first = false; + } + builder.append("}"); + } + + public ODocument toDocument(OIdentifiable source, OCommandContext ctx) { + String className = getClassNameForDocument(ctx); + ODocument doc; + if (className != null) { + doc = new ODocument(className); + } else { + doc = new ODocument(); + } + for (OJsonItem item : items) { + String name = item.getLeftValue(); + if (name == null) { + continue; + } + Object value; + if (item.right.value instanceof OJson) { + value = ((OJson) item.right.value).toDocument(source, ctx); + } else { + value = item.right.execute(source, ctx); + } + doc.field(name, value); + } + + return doc; + } + + public Map toMap(OIdentifiable source, OCommandContext ctx) { + String className = getClassNameForDocument(ctx); + Map doc = new HashMap(); + for (OJsonItem item : items) { + String name = item.getLeftValue(); + if (name == null) { + continue; + } + Object value = item.right.execute(source, ctx); + doc.put(name, value); + } + + return doc; + } + + private String getClassNameForDocument(OCommandContext ctx) { + for (OJsonItem item : items) { + String left = item.getLeftValue(); + if (left.toLowerCase(Locale.ENGLISH).equals("@class")) { + return "" + item.right.execute(null, ctx); + } + } + return null; + } +} +/* JavaCC - OriginalChecksum=3beec9f6db486de944498588b51a505d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJsonItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJsonItem.java new file mode 100644 index 00000000000..b2c8d9e9ee9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OJsonItem.java @@ -0,0 +1,37 @@ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +/** + * Created by luigidellaquila on 18/02/15. + */ +public class OJsonItem { + protected OIdentifier leftIdentifier; + protected String leftString; + protected OExpression right; + + public void toString(Map params, StringBuilder builder) { + if (leftIdentifier != null) { + builder.append("\""); + leftIdentifier.toString(params, builder); + builder.append("\""); + } + if (leftString != null) { + builder.append("\""); + builder.append(OExpression.encode(leftString)); + builder.append("\""); + } + builder.append(": "); + right.toString(params, builder); + } + + public String getLeftValue() { + if (leftString != null) { + return leftString; + } + if (leftIdentifier != null) { + leftIdentifier.getStringValue(); + } + return null; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLeOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLeOperator.java new file mode 100644 index 00000000000..5bd809d3edf --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLeOperator.java @@ -0,0 +1,50 @@ +/* Generated By:JJTree: Do not edit this line. OLeOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +public class OLeOperator extends SimpleNode implements OBinaryCompareOperator { + public OLeOperator(int id) { + super(id); + } + + public OLeOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + if (iLeft == null || iRight == null) { + return false;//only one is null, to check if both are null please use IS NULL + } + + if (iLeft.getClass() != iRight.getClass() && iLeft instanceof Number && iRight instanceof Number) { + Number[] couple = OType.castComparableNumber((Number) iLeft, (Number) iRight); + iLeft = couple[0]; + iRight = couple[1]; + } else { + iRight = OType.convert(iRight, iLeft.getClass()); + } + if (iRight == null) + return false; + return ((Comparable) iLeft).compareTo(iRight) <= 0; + } + + @Override + public String toString() { + return "<="; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=8b3232c970fd654af947274a5f384a93 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetClause.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetClause.java new file mode 100644 index 00000000000..959f45001c9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetClause.java @@ -0,0 +1,41 @@ +/* Generated By:JJTree: Do not edit this line. OLetClause.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OLetClause extends SimpleNode { + + protected List items = new ArrayList(); + + public OLetClause(int id) { + super(id); + } + + public OLetClause(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("LET "); + boolean first = true; + for (OLetItem item : items) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + } + + +} + +/* JavaCC - OriginalChecksum=201a864b5ed7f1fbe0533843a7acd03d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetItem.java new file mode 100644 index 00000000000..8d8060d115f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetItem.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OLetItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OLetItem extends SimpleNode { + + OIdentifier varName; + OExpression expression; + OStatement query; + + public OLetItem(int id) { + super(id); + } + + public OLetItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + varName.toString(params, builder); + builder.append(" = "); + if (expression != null) { + expression.toString(params, builder); + } else if (query != null) { + builder.append("("); + query.toString(params, builder); + builder.append(")"); + } + } + +} +/* JavaCC - OriginalChecksum=bb3cd298d79f50d72f6842e6d6ea4fb2 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetStatement.java new file mode 100644 index 00000000000..764493f643c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLetStatement.java @@ -0,0 +1,32 @@ +/* Generated By:JJTree: Do not edit this line. OLetStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OLetStatement extends OStatement { + protected OIdentifier name; + + protected OStatement statement; + protected OExpression expression; + + public OLetStatement(int id) { + super(id); + } + + public OLetStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("LET "); + name.toString(params, builder); + builder.append(" = "); + if (statement != null) { + statement.toString(params, builder); + } else { + expression.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=cc646e5449351ad9ced844f61b687928 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLevelZeroIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLevelZeroIdentifier.java new file mode 100644 index 00000000000..3eacad1f7d1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLevelZeroIdentifier.java @@ -0,0 +1,74 @@ +/* Generated By:JJTree: Do not edit this line. OLevelZeroIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Map; + +public class OLevelZeroIdentifier extends SimpleNode { + protected OFunctionCall functionCall; + protected Boolean self; + protected OCollection collection; + + public OLevelZeroIdentifier(int id) { + super(id); + } + + public OLevelZeroIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (functionCall != null) { + functionCall.toString(params, builder); + } else if (Boolean.TRUE.equals(self)) { + builder.append("@this"); + } else if (collection != null) { + collection.toString(params, builder); + } + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (functionCall != null) { + return functionCall.execute(iCurrentRecord, ctx); + } + if (collection != null) { + return collection.execute(iCurrentRecord, ctx); + } + if (Boolean.TRUE.equals(self)) { + return iCurrentRecord; + } + throw new UnsupportedOperationException(); + } + + public boolean isIndexedFunctionCall() { + if (functionCall != null) { + return functionCall.isIndexedFunctionCall(); + } + return false; + } + + public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) { + if (functionCall != null) { + return functionCall.estimateIndexedFunction(target, context, operator, right); + } + + return -1; + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context, + OBinaryCompareOperator operator, Object right) { + if (functionCall != null) { + return functionCall.executeIndexedFunction(target, context, operator, right); + } + return null; + } +} +/* JavaCC - OriginalChecksum=0305fcf120ba9395b4c975f85cdade72 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLikeOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLikeOperator.java new file mode 100644 index 00000000000..55253afad22 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLikeOperator.java @@ -0,0 +1,41 @@ +/* Generated By:JJTree: Do not edit this line. OLikeOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.query.OQueryHelper; + +public class OLikeOperator extends SimpleNode implements OBinaryCompareOperator { + public OLikeOperator(int id) { + super(id); + } + + public OLikeOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + if (OMultiValue.isMultiValue(iLeft) || OMultiValue.isMultiValue(iRight)) + return false; + + return OQueryHelper.like(iLeft.toString(), iRight.toString()); + } + + @Override + public String toString() { + return "LIKE"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=16d302abf0f85b404e57b964606952ca (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLimit.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLimit.java new file mode 100644 index 00000000000..b1a33ac5752 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLimit.java @@ -0,0 +1,38 @@ +/* Generated By:JJTree: Do not edit this line. OLimit.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OLimit extends SimpleNode { + + protected OInteger num; + + protected OInputParameter inputParam; + + public OLimit(int id) { + super(id); + } + + public OLimit(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (num == null && inputParam == null) { + return; + } + builder.append(" LIMIT "); + if (num != null) { + num.toString(params, builder); + } else { + inputParam.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=1063b9489290bb08de6048ba55013171 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLtOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLtOperator.java new file mode 100644 index 00000000000..9919343ace1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLtOperator.java @@ -0,0 +1,48 @@ +/* Generated By:JJTree: Do not edit this line. OLtOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.metadata.schema.OType; + +public class OLtOperator extends SimpleNode implements OBinaryCompareOperator { + public OLtOperator(int id) { + super(id); + } + + public OLtOperator(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean execute(Object iLeft, Object iRight) { + if (iLeft == null || iRight == null) { + return false;//only one is null, to check if both are null please use IS NULL + } + if (iLeft.getClass() != iRight.getClass() && iLeft instanceof Number && iRight instanceof Number) { + Number[] couple = OType.castComparableNumber((Number) iLeft, (Number) iRight); + iLeft = couple[0]; + iRight = couple[1]; + } else { + iRight = OType.convert(iRight, iLeft.getClass()); + } + if (iRight == null) + return false; + return ((Comparable) iLeft).compareTo(iRight) < 0; + } + + @Override public String toString() { + return "<"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + +} +/* JavaCC - OriginalChecksum=d8e97d52128198b373bb0c272c72de2c (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLuceneOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLuceneOperator.java new file mode 100644 index 00000000000..2767d7ec596 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OLuceneOperator.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OLuceneOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OLuceneOperator extends SimpleNode implements OBinaryCompareOperator { + public OLuceneOperator(int id) { + super(id); + } + + public OLuceneOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + throw new UnsupportedOperationException(toString() + " operator cannot be evaluated in this context"); + } + + @Override + public String toString() { + return "LUCENE"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=bda1e010e6ba48c815829b22ce458b9d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchExpression.java new file mode 100644 index 00000000000..9abf184d7e9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchExpression.java @@ -0,0 +1,33 @@ +/* Generated By:JJTree: Do not edit this line. OMatchExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OMatchExpression extends SimpleNode { + protected OMatchFilter origin; + protected List items = new ArrayList(); + + public OMatchExpression(int id) { + super(id); + } + + public OMatchExpression(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + origin.toString(params, builder); + for (OMatchPathItem item : items) { + item.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=73491fb653c32baf66997290db29f370 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilter.java new file mode 100644 index 00000000000..64dc1677ea0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilter.java @@ -0,0 +1,152 @@ +/* Generated By:JJTree: Do not edit this line. OMatchFilter.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.id.ORecordId; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OMatchFilter extends SimpleNode { + // TODO transform in a map + protected List items = new ArrayList(); + + public OMatchFilter(int id) { + super(id); + } + + public OMatchFilter(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public String getAlias() { + for (OMatchFilterItem item : items) { + if (item.alias != null) { + return item.alias.getStringValue(); + } + } + return null; + } + + public void setAlias(String alias) { + boolean found = false; + for (OMatchFilterItem item : items) { + if (item.alias != null) { + item.alias = new OIdentifier(-1); + item.alias.setValue(alias); + found = true; + break; + } + } + if (!found) { + OMatchFilterItem newItem = new OMatchFilterItem(-1); + newItem.alias = new OIdentifier(-1); + newItem.alias.setValue(alias); + items.add(newItem); + } + } + + public OWhereClause getFilter() { + for (OMatchFilterItem item : items) { + if (item.filter != null) { + return item.filter; + } + } + return null; + } + + public void setFilter(OWhereClause filter) { + boolean found = false; + for (OMatchFilterItem item : items) { + if (item.filter != null) { + item.filter = filter; + found = true; + break; + } + } + if (!found) { + OMatchFilterItem newItem = new OMatchFilterItem(-1); + newItem.filter = filter; + items.add(newItem); + } + } + + public OWhereClause getWhileCondition() { + for (OMatchFilterItem item : items) { + if (item.whileCondition != null) { + return item.whileCondition; + } + } + return null; + } + + public String getClassName(OCommandContext context) { + for (OMatchFilterItem item : items) { + if (item.className != null) { + if (item.className.value instanceof String) + return (String) item.className.value; + else if (item.className.value instanceof SimpleNode) { + StringBuilder builder = new StringBuilder(); + + ((SimpleNode) item.className.value).toString(context == null ? null : context.getInputParameters(), builder); + return builder.toString(); + } else { + return item.className.value.toString(); + } + } + } + return null; + } + + public ORID getRid(OCommandContext ctx) { + for (OMatchFilterItem item : items) { + if (item.rid != null) { + return new ORecordId(item.rid.cluster.getValue().intValue(), item.rid.position.getValue().longValue()); + } + } + return null; + } + + public Integer getMaxDepth() { + for (OMatchFilterItem item : items) { + if (item.maxDepth != null) { + return item.maxDepth.value.intValue(); + } + } + return null; + } + + public boolean isOptional() { + for (OMatchFilterItem item : items) { + if (Boolean.TRUE.equals(item.optional)) { + return true; + } + } + return false; + } + + public void toString(Map params, StringBuilder builder) { + builder.append("{"); + boolean first = true; + for (OMatchFilterItem item : items) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + builder.append("}"); + } + +} +/* JavaCC - OriginalChecksum=6b099371c69e0d0c1c106fc96b3072de (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilterItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilterItem.java new file mode 100644 index 00000000000..6c0fd67b0eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchFilterItem.java @@ -0,0 +1,85 @@ +/* Generated By:JJTree: Do not edit this line. OMatchFilterItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OMatchFilterItem extends SimpleNode { + protected ORid rid; + protected OExpression className; + protected OExpression classNames; + protected OIdentifier alias; + protected OWhereClause filter; + protected OWhereClause whileCondition; + protected OArrayRangeSelector depth; + protected OInteger maxDepth; + protected Boolean optional; + + public OMatchFilterItem(int id) { + super(id); + } + + public OMatchFilterItem(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (rid != null) { + builder.append("rid: "); + rid.toString(params, builder); + return; + } + if (className != null) { + builder.append("class: "); + className.toString(params, builder); + return; + } + if (classNames != null) { + builder.append("classes: "); + classNames.toString(params, builder); + return; + } + + if (alias != null) { + builder.append("as: "); + alias.toString(params, builder); + return; + } + + if (maxDepth != null) { + builder.append("maxdepth: "); + maxDepth.toString(params, builder); + return; + } + + if (filter != null) { + builder.append("where: ("); + filter.toString(params, builder); + builder.append(")"); + return; + } + + if (whileCondition != null) { + builder.append("while: ("); + whileCondition.toString(params, builder); + builder.append(")"); + return; + } + + if (optional != null) { + builder.append("optional: "); + builder.append(optional); + return; + } + } + + +} +/* JavaCC - OriginalChecksum=74bf4765509f102180cac29f2295031e (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItem.java new file mode 100644 index 00000000000..785d71cb5c3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItem.java @@ -0,0 +1,96 @@ +/* Generated By:JJTree: Do not edit this line. OMatchPathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +public class OMatchPathItem extends SimpleNode { + protected OMethodCall method; + protected OMatchFilter filter; + + public OMatchPathItem(int id) { + super(id); + } + + public OMatchPathItem(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean isBidirectional() { + if (filter.getWhileCondition() != null) { + return false; + } + if (filter.getMaxDepth() != null) { + return false; + } + if (filter.isOptional()) { + return false; + } + return method.isBidirectional(); + } + + public void toString(Map params, StringBuilder builder) { + method.toString(params, builder); + if (filter != null) { + filter.toString(params, builder); + } + } + + protected Iterable executeTraversal(final OMatchStatement.MatchContext matchContext, + final OCommandContext iCommandContext, final OIdentifiable startingPoint, int depth) { + return new Iterable() { + @Override public Iterator iterator() { + return new OMatchPathItemIterator(OMatchPathItem.this, matchContext, iCommandContext, startingPoint); + } + }; + } + + protected boolean matchesClass(OIdentifiable identifiable, OClass oClass) { + if (identifiable == null) { + return false; + } + ORecord record = identifiable.getRecord(); + if (record == null) { + return false; + } + if (record instanceof ODocument) { + return ((ODocument) record).getSchemaClass().isSubClassOf(oClass); + } + return false; + } + + protected Iterable traversePatternEdge(OMatchStatement.MatchContext matchContext, OIdentifiable startingPoint, + OCommandContext iCommandContext) { + + Iterable possibleResults = null; + if (filter != null) { + OIdentifiable matchedNode = matchContext.matched.get(filter.getAlias()); + if (matchedNode != null) { + possibleResults = Collections.singleton(matchedNode); + } else if (matchContext.matched.containsKey(filter.getAlias())) { + possibleResults = Collections.emptySet();//optional node, the matched element is a null value + } else { + possibleResults = matchContext.candidates == null ? null : matchContext.candidates.get(filter.getAlias()); + } + } + + Object qR = this.method.execute(startingPoint, possibleResults, iCommandContext); + return (qR instanceof Iterable) ? (Iterable) qR : Collections.singleton(qR); + } +} +/* JavaCC - OriginalChecksum=ffe8e0ffde583d7b21c9084eff6a8944 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemFirst.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemFirst.java new file mode 100644 index 00000000000..56bf50adb1e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemFirst.java @@ -0,0 +1,40 @@ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.Map; + +/** + * @author Luigi Dell'Aquila + */ +public class OMatchPathItemFirst extends OMatchPathItem { + protected OFunctionCall function; + + public OMatchPathItemFirst(int id) { + super(id); + } + + public OMatchPathItemFirst(OrientSql p, int id) { + super(p, id); + } + + public boolean isBidirectional() { + return false; + } + + public void toString(Map params, StringBuilder builder) { + + function.toString(params, builder); + if (filter != null) { + filter.toString(params, builder); + } + } + + protected Iterable traversePatternEdge(OMatchStatement.MatchContext matchContext, OIdentifiable startingPoint, + OCommandContext iCommandContext) { + Object qR = this.function.execute(startingPoint, iCommandContext); + return (qR instanceof Iterable) ? (Iterable) qR : Collections.singleton(qR); + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemIterator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemIterator.java new file mode 100644 index 00000000000..f7c12e39967 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchPathItemIterator.java @@ -0,0 +1,182 @@ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by luigidellaquila on 14/11/16. + */ +public class OMatchPathItemIterator implements Iterator { + + private final OMatchStatement.MatchContext matchContext; + private final OCommandContext ctx; + OMatchPathItem item; + + OIdentifiable nextElement = null; + + List stack = new LinkedList(); + + OMatchPathItemIterator(OMatchPathItem item, OMatchStatement.MatchContext matchContext, OCommandContext iCommandContext, + final OIdentifiable startingPoint) { + this.item = item; + this.matchContext = matchContext; + this.ctx = iCommandContext; + this.stack.add(new Iterator() { + boolean executed = false; + + @Override + public boolean hasNext() { + return !executed; + } + + @Override + public Object next() { + if (executed) { + throw new IllegalStateException(); + } + executed = true; + return startingPoint; + } + + @Override + public void remove() { + + } + }); + + loadNext(); + } + + protected void loadNext() { + while (stack.size() > 0) { + if (!stack.get(0).hasNext()) { + stack.remove(0); + continue; + } + loadNextInternal(); + if (nextElement != null) { + return; + } + } + } + + protected void loadNextInternal() { + if (stack == null || stack.size() == 0) { + nextElement = null; + return; + } + + int depth = stack.size() - 1; + + Object oldDepth = ctx.getVariable("$depth", depth); + ctx.setVariable("$depth", depth); + + final OWhereClause filter = this.item.filter == null ? null : this.item.filter.getFilter(); + final String className = this.item.filter == null ? null : this.item.filter.getClassName(ctx); + final OClass clazz = + className == null ? null : ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().getClass(className); + final OWhereClause whileCondition = this.item.filter == null ? null : this.item.filter.getWhileCondition(); + final Integer maxDepth = maxDepth(this.item.filter); + final OClass oClass = + this.item.filter == null ? null : SimpleNode.getDatabase().getMetadata().getSchema().getClass(this.item.filter.getClassName(ctx)); + + boolean notDeep = this.item.filter == null || (whileCondition == null && this.item.filter.getMaxDepth() == null); + + OIdentifiable startingPoint = (OIdentifiable) stack.get(0).next(); + nextElement = null; + if (this.item.filter == null || (whileCondition == null && this.item.filter.getMaxDepth() == null)) { + //basic case, no traversal, discard level zero + if (depth == 1) { + Object prevMatch = ctx.getVariable("$currentMatch"); + Object prevCurrent = ctx.getVariable("$current"); + ctx.setVariable("$currentMatch", startingPoint); + ctx.setVariable("$current", startingPoint); + + if ((filter == null || filter.matchesFilters(startingPoint, ctx)) && (clazz == null || clazz + .isSuperClassOf(((ODocument) startingPoint.getRecord()).getSchemaClass()))) { + nextElement = startingPoint; + } + ctx.setVariable("$current", prevCurrent); + ctx.setVariable("$currentMatch", prevMatch); + } + } else { + Object prevMatch = ctx.getVariable("$currentMatch"); + ctx.setVariable("$currentMatch", startingPoint); + if ((filter == null || filter.matchesFilters(startingPoint, ctx)) && (clazz == null || clazz + .isSuperClassOf(((ODocument) startingPoint.getRecord()).getSchemaClass()))) { + nextElement = startingPoint; + } + ctx.setVariable("$currentMatch", prevMatch); + } + + if ((notDeep && depth == 0) || ((maxDepth == null || depth < maxDepth) && (whileCondition == null || whileCondition + .matchesFilters(startingPoint, ctx)) +// && (oClass == null || matchesClass(oClass, startingPoint)) + )) { + stack.add(0, item.traversePatternEdge(matchContext, startingPoint, ctx).iterator()); + } + ctx.setVariable("$depth", oldDepth); + } + + @Override + public boolean hasNext() { + while (stack.size() > 0 && nextElement == null) { + loadNext(); + } + return nextElement != null; + } + + private Integer maxDepth(OMatchFilter filter) { + if (filter == null) { + return 1; + } + if (filter.getMaxDepth() != null) { + return filter.getMaxDepth(); + } + if (filter.getWhileCondition() == null) { + return 1; + } + return null; + } + + private boolean matchesClass(OClass oClass, OIdentifiable startingPoint) { + if (oClass == null) { + return true; + } + ODocument doc = startingPoint.getRecord(); + if (doc == null) { + return false; + } + OClass clazz = doc.getSchemaClass(); + if (clazz == null) { + return false; + } + return clazz.isSubClassOf(oClass); + } + + @Override + public OIdentifiable next() { + if (nextElement == null) { + throw new IllegalStateException(); + } + OIdentifiable result = nextElement; + nextElement = null; + while (stack.size() > 0 && nextElement == null) { + loadNext(); + } + return result; + } + + @Override + public void remove() { + + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchStatement.java new file mode 100644 index 00000000000..c19a3496e7c --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchStatement.java @@ -0,0 +1,1352 @@ +/* Generated By:JJTree: Do not edit this line. OMatchStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.exception.OErrorCode; +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.common.util.OPair; +import com.orientechnologies.orient.core.command.*; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.document.ODatabaseDocument; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OSchema; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLResultsetDelegate; +import com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.OIterableRecordSource; +import com.orientechnologies.orient.core.sql.filter.OSQLTarget; +import com.orientechnologies.orient.core.sql.query.OBasicResultSet; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.*; + +public class OMatchStatement extends OStatement implements OCommandExecutor, OIterableRecordSource { + + String DEFAULT_ALIAS_PREFIX = "$ORIENT_DEFAULT_ALIAS_"; + + private OSQLAsynchQuery request; + + long threshold = 20; + private int limitFromProtocol = -1; + + class MatchContext { + int currentEdgeNumber = 0; + + Map candidates = new LinkedHashMap(); + Map matched = new LinkedHashMap(); + Map matchedEdges = new IdentityHashMap(); + + public MatchContext copy(String alias, OIdentifiable value) { + MatchContext result = new MatchContext(); + + result.candidates.putAll(candidates); + result.candidates.remove(alias); + + result.matched.putAll(matched); + result.matched.put(alias, value); + + result.matchedEdges.putAll(matchedEdges); + result.currentEdgeNumber = currentEdgeNumber; + return result; + } + + public ODocument toDoc() { + ODocument doc = new ODocument(); + doc.fromMap((Map) matched); + return doc; + } + + } + + public static class EdgeTraversal { + boolean out = true; + PatternEdge edge; + + public EdgeTraversal(PatternEdge edge, boolean out) { + this.edge = edge; + this.out = out; + } + } + + public static class MatchExecutionPlan { + public List sortedEdges; + public Map preFetchedAliases = new HashMap(); + public String rootAlias; + } + + public static final String KEYWORD_MATCH = "MATCH"; + // parsed data + protected List matchExpressions = new ArrayList(); + protected List returnItems = new ArrayList(); + protected List returnAliases = new ArrayList(); + protected OLimit limit; + + protected Pattern pattern; + + private Map aliasFilters; + private Map aliasClasses; + private Map aliasRids; + + // execution data + private OCommandContext context; + private OProgressListener progressListener; + + public OMatchStatement() { + super(-1); + } + + public OMatchStatement(int id) { + super(id); + } + + public OMatchStatement(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + // ------------------------------------------------------------------ + // query parsing and optimization + // ------------------------------------------------------------------ + + /** + * this method parses the statement + * + * @param iRequest Command request implementation. + * @param + * + * @return + */ + @Override + public RET parse(OCommandRequest iRequest) { + final OCommandRequestText textRequest = (OCommandRequestText) iRequest; + if (iRequest instanceof OSQLSynchQuery) { + request = (OSQLSynchQuery) iRequest; + } else if (iRequest instanceof OSQLAsynchQuery) { + request = (OSQLAsynchQuery) iRequest; + } else { + // BUILD A QUERY OBJECT FROM THE COMMAND REQUEST + request = new OSQLSynchQuery(textRequest.getText()); + if (textRequest.getResultListener() != null) { + request.setResultListener(textRequest.getResultListener()); + } + } + String queryText = textRequest.getText(); + + // please, do not look at this... refactor this ASAP with new executor structure + final InputStream is = new ByteArrayInputStream(queryText.getBytes()); + final OrientSql osql = new OrientSql(is); + try { + OMatchStatement result = (OMatchStatement) osql.parse(); + this.matchExpressions = result.matchExpressions; + this.returnItems = result.returnItems; + this.returnAliases = result.returnAliases; + this.limit = result.limit; + } catch (ParseException e) { + OCommandSQLParsingException ex = new OCommandSQLParsingException(e, queryText); + OErrorCode.QUERY_PARSE_ERROR.throwException(ex.getMessage(), ex); + } + + assignDefaultAliases(this.matchExpressions); + pattern = new Pattern(); + for (OMatchExpression expr : this.matchExpressions) { + pattern.addExpression(expr); + } + + Map aliasFilters = new LinkedHashMap(); + Map aliasClasses = new LinkedHashMap(); + Map aliasRids = new LinkedHashMap(); + for (OMatchExpression expr : this.matchExpressions) { + addAliases(expr, aliasFilters, aliasClasses, aliasRids, context); + } + + this.aliasFilters = aliasFilters; + this.aliasClasses = aliasClasses; + this.aliasRids = aliasRids; + + rebindFilters(aliasFilters); + + pattern.validate(); + + return (RET) this; + } + + /** + * rebinds filter (where) conditions to alias nodes after optimization + * + * @param aliasFilters + */ + private void rebindFilters(Map aliasFilters) { + for (OMatchExpression expression : matchExpressions) { + OWhereClause newFilter = aliasFilters.get(expression.origin.getAlias()); + expression.origin.setFilter(newFilter); + + for (OMatchPathItem item : expression.items) { + newFilter = aliasFilters.get(item.filter.getAlias()); + item.filter.setFilter(newFilter); + } + } + } + + /** + * assigns default aliases to pattern nodes that do not have an explicit alias + * + * @param matchExpressions + */ + private void assignDefaultAliases(List matchExpressions) { + int counter = 0; + for (OMatchExpression expression : matchExpressions) { + if (expression.origin.getAlias() == null) { + expression.origin.setAlias(DEFAULT_ALIAS_PREFIX + (counter++)); + } + + for (OMatchPathItem item : expression.items) { + if (item.filter == null) { + item.filter = new OMatchFilter(-1); + } + if (item.filter.getAlias() == null) { + item.filter.setAlias(DEFAULT_ALIAS_PREFIX + (counter++)); + } + } + } + } + + // ------------------------------------------------------------------ + // query execution + // ------------------------------------------------------------------ + + /** + * this method works statefully, using request and context variables from current Match statement. This method will be deprecated + * in next releases + * + * @param iArgs Optional variable arguments to pass to the command. + * + * @return + */ + @Override + public Object execute(Map iArgs) { + this.context.setInputParameters(iArgs); + return execute(this.request, this.context, this.progressListener); + } + + /** + * executes the match statement. This is the preferred execute() method and it has to be used as the default one in the future. + * This method works in stateless mode + * + * @param request + * @param context + * + * @return + */ + public Object execute(OSQLAsynchQuery request, OCommandContext context, OProgressListener progressListener) { + Map iArgs = context.getInputParameters(); + try { + + Map estimatedRootEntries = estimateRootEntries(aliasClasses, aliasFilters, aliasRids, context); + if (estimatedRootEntries.values().contains(0l)) { + return new OBasicResultSet();// some aliases do not match on any classes + } + + List sortedEdges = getTopologicalSortedSchedule(estimatedRootEntries, pattern); + MatchExecutionPlan executionPlan = new MatchExecutionPlan(); + executionPlan.sortedEdges = sortedEdges; + + calculateMatch(pattern, estimatedRootEntries, new MatchContext(), aliasClasses, aliasFilters, aliasRids, context, request, + executionPlan); + + return getResult(request); + } finally { + if (request.getResultListener() != null) { + request.getResultListener().end(); + } + } + + } + + /** + * Start a depth-first traversal from the starting node, adding all viable unscheduled edges and vertices. + * + * @param startNode the node from which to start the depth-first traversal + * @param visitedNodes set of nodes that are already visited (mutated in this function) + * @param visitedEdges set of edges that are already visited and therefore don't need to be scheduled (mutated in this + * function) + * @param remainingDependencies dependency map including only the dependencies that haven't yet been satisfied (mutated in this + * function) + * @param resultingSchedule the schedule being computed i.e. appended to (mutated in this function) + */ + private void updateScheduleStartingAt(PatternNode startNode, Set visitedNodes, Set visitedEdges, + Map> remainingDependencies, List resultingSchedule) { + // OrientDB requires the schedule to contain all edges present in the query, which is a stronger condition + // than simply visiting all nodes in the query. Consider the following example query: + // MATCH { + // class: A, + // as: foo + // }.in() { + // as: bar + // }, { + // class: B, + // as: bar + // }.out() { + // as: foo + // } RETURN $matches + // The schedule for the above query must have two edges, even though there are only two nodes and they can both + // be visited with the traversal of a single edge. + // + // To satisfy it, we obey the following for each non-optional node: + // - ignore edges to neighboring nodes which have unsatisfied dependencies; + // - for visited neighboring nodes, add their edge if it wasn't already present in the schedule, but do not + // recurse into the neighboring node; + // - for unvisited neighboring nodes with satisfied dependencies, add their edge and recurse into them. + visitedNodes.add(startNode); + for (Set dependencies : remainingDependencies.values()) { + dependencies.remove(startNode.alias); + } + + Map edges = new LinkedHashMap(); + for (PatternEdge outEdge : startNode.out) { + edges.put(outEdge, true); + } + for (PatternEdge inEdge : startNode.in) { + edges.put(inEdge, false); + } + + for (Map.Entry edgeData : edges.entrySet()) { + PatternEdge edge = edgeData.getKey(); + boolean isOutbound = edgeData.getValue(); + PatternNode neighboringNode = isOutbound ? edge.in : edge.out; + + if (!remainingDependencies.get(neighboringNode.alias).isEmpty()) { + // Unsatisfied dependencies, ignore this neighboring node. + continue; + } + + if (visitedNodes.contains(neighboringNode)) { + if (!visitedEdges.contains(edge)) { + // If we are executing in this block, we are in the following situation: + // - the startNode has not been visited yet; + // - it has a neighboringNode that has already been visited; + // - the edge between the startNode and the neighboringNode has not been scheduled yet. + // + // The isOutbound value shows us whether the edge is outbound from the point of view of the startNode. + // However, if there are edges to the startNode, we must visit the startNode from an already-visited + // neighbor, to preserve the validity of the traversal. Therefore, we negate the value of isOutbound + // to ensure that the edge is always scheduled in the direction from the already-visited neighbor + // toward the startNode. Notably, this is also the case when evaluating "optional" nodes -- we always + // visit the optional node from its non-optional and already-visited neighbor. + // + // The only exception to the above is when we have edges with "while" conditions. We are not allowed + // to flip their directionality, so we leave them as-is. + boolean traversalDirection; + if (startNode.optional || edge.item.isBidirectional()) { + traversalDirection = !isOutbound; + } else { + traversalDirection = isOutbound; + } + + visitedEdges.add(edge); + resultingSchedule.add(new EdgeTraversal(edge, traversalDirection)); + } + } else if (!startNode.optional) { + // If the neighboring node wasn't visited, we don't expand the optional node into it, hence the above check. + // Instead, we'll allow the neighboring node to add the edge we failed to visit, via the above block. + if (visitedEdges.contains(edge)) { + // Should never happen. + throw new AssertionError("The edge was visited, but the neighboring vertex was not: " + edge + " " + neighboringNode); + } + + visitedEdges.add(edge); + resultingSchedule.add(new EdgeTraversal(edge, isOutbound)); + updateScheduleStartingAt(neighboringNode, visitedNodes, visitedEdges, remainingDependencies, resultingSchedule); + } + } + } + + /** + * Calculate the set of dependency aliases for each alias in the pattern. + * + * @param pattern + * + * @return map of alias to the set of aliases it depends on + */ + private Map> getDependencies(Pattern pattern) { + Map> result = new HashMap>(); + + for (PatternNode node : pattern.aliasToNode.values()) { + Set currentDependencies = new HashSet(); + + OWhereClause filter = aliasFilters.get(node.alias); + if (filter != null && filter.baseExpression != null) { + List involvedAliases = filter.baseExpression.getMatchPatternInvolvedAliases(); + if (involvedAliases != null) { + currentDependencies.addAll(involvedAliases); + } + } + + result.put(node.alias, currentDependencies); + } + + return result; + } + + /** + * sort edges in the order they will be matched + */ + private List getTopologicalSortedSchedule(Map estimatedRootEntries, Pattern pattern) { + List resultingSchedule = new ArrayList(); + Map> remainingDependencies = getDependencies(pattern); + Set visitedNodes = new HashSet(); + Set visitedEdges = new HashSet(); + + // Sort the possible root vertices in order of estimated size, since we want to start with a small vertex set. + List> rootWeights = new ArrayList>(); + for (Map.Entry root : estimatedRootEntries.entrySet()) { + rootWeights.add(new OPair(root.getValue(), root.getKey())); + } + Collections.sort(rootWeights); + + // Add the starting vertices, in the correct order, to an ordered set. + Set remainingStarts = new LinkedHashSet(); + for (OPair item : rootWeights) { + remainingStarts.add(item.getValue()); + } + // Add all the remaining aliases after all the suggested start points. + for (String alias : pattern.aliasToNode.keySet()) { + if (!remainingStarts.contains(alias)) { + remainingStarts.add(alias); + } + } + + while (resultingSchedule.size() < pattern.numOfEdges) { + // Start a new depth-first pass, adding all nodes with satisfied dependencies. + // 1. Find a starting vertex for the depth-first pass. + PatternNode startingNode = null; + List startsToRemove = new ArrayList(); + for (String currentAlias : remainingStarts) { + PatternNode currentNode = pattern.aliasToNode.get(currentAlias); + + if (visitedNodes.contains(currentNode)) { + // If a previous traversal already visited this alias, remove it from further consideration. + startsToRemove.add(currentAlias); + } else if (remainingDependencies.get(currentAlias).isEmpty()) { + // If it hasn't been visited, and has all dependencies satisfied, visit it. + startsToRemove.add(currentAlias); + startingNode = currentNode; + break; + } + } + remainingStarts.removeAll(startsToRemove); + + if (startingNode == null) { + // We didn't manage to find a valid root, and yet we haven't constructed a complete schedule. + // This means there must be a cycle in our dependency graph, or all dependency-free nodes are optional. + // Therefore, the query is invalid. + throw new OCommandExecutionException("This query contains MATCH conditions that cannot be evaluated, " + + "like an undefined alias or a circular dependency on a $matched condition."); + } + + // 2. Having found a starting vertex, traverse its neighbors depth-first, + // adding any non-visited ones with satisfied dependencies to our schedule. + updateScheduleStartingAt(startingNode, visitedNodes, visitedEdges, remainingDependencies, resultingSchedule); + } + + if (resultingSchedule.size() != pattern.numOfEdges) { + throw new AssertionError("Incorrect number of edges: " + resultingSchedule.size() + " vs " + pattern.numOfEdges); + } + + return resultingSchedule; + } + + protected Object getResult(OSQLAsynchQuery request) { + if (request instanceof OSQLSynchQuery) + return ((OSQLSynchQuery) request).getResult(); + + return null; + } + + private boolean calculateMatch(Pattern pattern, Map estimatedRootEntries, MatchContext matchContext, + Map aliasClasses, Map aliasFilters, Map aliasRids, + OCommandContext iCommandContext, OSQLAsynchQuery request, MatchExecutionPlan executionPlan) { + + boolean rootFound = false; + // find starting nodes with few entries + for (Map.Entry entryPoint : estimatedRootEntries.entrySet()) { + if (entryPoint.getValue() < threshold) { + String nextAlias = entryPoint.getKey(); + Iterable matches = fetchAliasCandidates(nextAlias, aliasFilters, iCommandContext, aliasClasses, aliasRids); + + Set ids = new HashSet(); + if (!matches.iterator().hasNext()) { + if (pattern.get(nextAlias).isOptionalNode()) { + continue; + } + return true; + } + + matchContext.candidates.put(nextAlias, matches); + executionPlan.preFetchedAliases.put(nextAlias, entryPoint.getValue()); + rootFound = true; + } + } + // no nodes under threshold, guess the smallest one + if (!rootFound) { + String nextAlias = getNextAlias(estimatedRootEntries, matchContext); + Iterable matches = fetchAliasCandidates(nextAlias, aliasFilters, iCommandContext, aliasClasses, aliasRids); + if (!matches.iterator().hasNext()) { + return true; + } + matchContext.candidates.put(nextAlias, matches); + executionPlan.preFetchedAliases.put(nextAlias, estimatedRootEntries.get(nextAlias)); + } + + // pick first edge (as sorted before) + EdgeTraversal firstEdge = executionPlan.sortedEdges.size() == 0 ? null : executionPlan.sortedEdges.get(0); + String smallestAlias = null; + // and choose the most convenient starting point (the most convenient traversal direction) + if (firstEdge != null) { + smallestAlias = firstEdge.out ? firstEdge.edge.out.alias : firstEdge.edge.in.alias; + } else { + smallestAlias = pattern.aliasToNode.values().iterator().next().alias; + } + executionPlan.rootAlias = smallestAlias; + Iterable allCandidates = matchContext.candidates.get(smallestAlias); + if (allCandidates == null) { + OSelectStatement select = buildSelectStatement(aliasClasses.get(smallestAlias), aliasFilters.get(smallestAlias)); + allCandidates = (Iterable) getDatabase().query(new OSQLSynchQuery(select.toString())); + } + + if (!processContextFromCandidates(pattern, executionPlan, matchContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request, allCandidates, smallestAlias, 0)) { + return false; + } + return true; + } + + private boolean processContextFromCandidates(Pattern pattern, MatchExecutionPlan executionPlan, MatchContext matchContext, + Map aliasClasses, Map aliasFilters, Map aliasRids, + OCommandContext iCommandContext, OSQLAsynchQuery request, Iterable candidates, String alias, + int startFromEdge) { + for (OIdentifiable id : candidates) { + MatchContext childContext = matchContext.copy(alias, id); + childContext.currentEdgeNumber = startFromEdge; + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, request)) { + return false; + } + } + return true; + } + + private Iterable fetchAliasCandidates(String nextAlias, Map aliasFilters, + OCommandContext iCommandContext, Map aliasClasses, Map aliasRids) { + Iterator it = query(aliasClasses.get(nextAlias), aliasFilters.get(nextAlias), aliasRids.get(nextAlias), + iCommandContext); + Set result = new HashSet(); + while (it.hasNext()) { + result.add(it.next().getIdentity()); + } + + return result; + } + + private boolean processContext(Pattern pattern, MatchExecutionPlan executionPlan, MatchContext matchContext, + Map aliasClasses, Map aliasFilters, Map aliasRids, + OCommandContext iCommandContext, OSQLAsynchQuery request) { + + iCommandContext.setVariable("$matched", matchContext.matched); + + if (pattern.getNumOfEdges() == matchContext.matchedEdges.size() && allNodesCalculated(matchContext, pattern)) { + // false if limit reached + return addResult(matchContext, request, iCommandContext); + } + if (executionPlan.sortedEdges.size() == matchContext.currentEdgeNumber) { + // false if limit reached + return expandCartesianProduct(pattern, matchContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, request); + } + EdgeTraversal currentEdge = executionPlan.sortedEdges.get(matchContext.currentEdgeNumber); + PatternNode rootNode = currentEdge.out ? currentEdge.edge.out : currentEdge.edge.in; + + if (currentEdge.out) { + PatternEdge outEdge = currentEdge.edge; + + if (!matchContext.matchedEdges.containsKey(outEdge)) { + + OIdentifiable startingPoint = matchContext.matched.get(outEdge.out.alias); + if (startingPoint == null) { + //restart from candidates (disjoint patterns? optional? just could not proceed from last node?) + Iterable rightCandidates = matchContext.candidates.get(outEdge.out.alias); + if (rightCandidates != null) { + if (!processContextFromCandidates(pattern, executionPlan, matchContext, aliasClasses, aliasFilters, aliasRids, + iCommandContext, request, rightCandidates, outEdge.out.alias, matchContext.currentEdgeNumber)) { + return false; + } + } + return true; + } + Object rightValues = outEdge.executeTraversal(matchContext, iCommandContext, startingPoint, 0); + + if (outEdge.in.isOptionalNode() && (isEmptyResult(rightValues) || !contains(rightValues, + matchContext.matched.get(outEdge.in.alias)))) { + MatchContext childContext = matchContext.copy(outEdge.in.alias, null); + childContext.matched.put(outEdge.in.alias, null); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; //TODO testOptional 3 match passa con +1 + childContext.matchedEdges.put(outEdge, true); + + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + if (!(rightValues instanceof Iterable)) { + rightValues = Collections.singleton(rightValues); + } + String rightClassName = aliasClasses.get(outEdge.in.alias); + OClass rightClass = getDatabase().getMetadata().getSchema().getClass(rightClassName); + for (OIdentifiable rightValue : (Iterable) rightValues) { + if (rightValue == null) { + continue; //broken graph?, null reference + } + if (rightClass != null && !matchesClass(rightValue, rightClass)) { + continue; + } + Iterable prevMatchedRightValues = matchContext.candidates.get(outEdge.in.alias); + + if (matchContext.matched.containsKey(outEdge.in.alias)) { + if (matchContext.matched.get(outEdge.in.alias).getIdentity().equals(rightValue.getIdentity())) { + MatchContext childContext = matchContext.copy(outEdge.in.alias, rightValue.getIdentity()); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(outEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + break; + } + } else if (prevMatchedRightValues != null && prevMatchedRightValues.iterator().hasNext()) {// just matching against + // known + // values + for (OIdentifiable id : prevMatchedRightValues) { + if (id.getIdentity().equals(rightValue.getIdentity())) { + MatchContext childContext = matchContext.copy(outEdge.in.alias, id); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(outEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + } + } else {// searching for neighbors + MatchContext childContext = matchContext.copy(outEdge.in.alias, rightValue.getIdentity()); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(outEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + } + } + } else { + PatternEdge inEdge = currentEdge.edge; + if (!matchContext.matchedEdges.containsKey(inEdge)) { + if (!inEdge.item.isBidirectional()) { + throw new RuntimeException("Invalid pattern to match!"); + } + if (!matchContext.matchedEdges.containsKey(inEdge)) { + Object leftValues = inEdge.item.method.executeReverse(matchContext.matched.get(inEdge.in.alias), iCommandContext); + if (inEdge.out.isOptionalNode() && (isEmptyResult(leftValues) || !contains(leftValues, + matchContext.matched.get(inEdge.out.alias)))) { + MatchContext childContext = matchContext.copy(inEdge.out.alias, null); + childContext.matched.put(inEdge.out.alias, null); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(inEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + if (!(leftValues instanceof Iterable)) { + leftValues = Collections.singleton(leftValues); + } + + String leftClassName = aliasClasses.get(inEdge.out.alias); + OClass leftClass = getDatabase().getMetadata().getSchema().getClass(leftClassName); + + for (OIdentifiable leftValue : (Iterable) leftValues) { + if (leftValue == null) { + continue; //broken graph? null reference + } + if (leftClass != null && !matchesClass(leftValue, leftClass)) { + continue; + } + Iterable prevMatchedRightValues = matchContext.candidates.get(inEdge.out.alias); + + if (matchContext.matched.containsKey(inEdge.out.alias)) { + if (matchContext.matched.get(inEdge.out.alias).getIdentity().equals(leftValue.getIdentity())) { + MatchContext childContext = matchContext.copy(inEdge.out.alias, leftValue.getIdentity()); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(inEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + break; + } + } else if (prevMatchedRightValues != null && prevMatchedRightValues.iterator().hasNext()) {// just matching against + // known + // values + for (OIdentifiable id : prevMatchedRightValues) { + if (id.getIdentity().equals(leftValue.getIdentity())) { + MatchContext childContext = matchContext.copy(inEdge.out.alias, id); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(inEdge, true); + + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + } + } else { // searching for neighbors + OWhereClause where = aliasFilters.get(inEdge.out.alias); + String className = aliasClasses.get(inEdge.out.alias); + OClass oClass = getDatabase().getMetadata().getSchema().getClass(className); + if ((oClass == null || matchesClass(leftValue, oClass)) && (where == null || where + .matchesFilters(leftValue, iCommandContext))) { + MatchContext childContext = matchContext.copy(inEdge.out.alias, leftValue.getIdentity()); + childContext.currentEdgeNumber = matchContext.currentEdgeNumber + 1; + childContext.matchedEdges.put(inEdge, true); + if (!processContext(pattern, executionPlan, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request)) { + return false; + } + } + } + } + } + } + } + return true; + } + + private boolean matchesClass(OIdentifiable identifiable, OClass oClass) { + if (identifiable == null) { + return false; + } + ORecord record = identifiable.getRecord(); + if (record == null) { + return false; + } + if (record instanceof ODocument) { + OClass schemaClass = ((ODocument) record).getSchemaClass(); + if (schemaClass == null) { + return false; + } + return schemaClass.isSubClassOf(oClass); + } + return false; + } + + private boolean contains(Object rightValues, OIdentifiable oIdentifiable) { + if (oIdentifiable == null) { + return true; + } + if (rightValues == null) { + return false; + } + if (rightValues instanceof OIdentifiable) { + return ((OIdentifiable) rightValues).getIdentity().equals(oIdentifiable.getIdentity()); + } + Iterator iterator = null; + if (rightValues instanceof Iterable) { + iterator = ((Iterable) rightValues).iterator(); + } + if (rightValues instanceof Iterator) { + iterator = (Iterator) rightValues; + } + if (iterator != null) { + while (iterator.hasNext()) { + Object next = iterator.next(); + if (next instanceof OIdentifiable) { + if (((OIdentifiable) next).getIdentity().equals(oIdentifiable.getIdentity())) { + return true; + } + } + } + } + return false; + } + + private boolean isEmptyResult(Object rightValues) { + if (rightValues == null) { + return true; + } + if (rightValues instanceof Iterable) { + Iterator iterator = ((Iterable) rightValues).iterator(); + if (!iterator.hasNext()) { + return true; + } + while (iterator.hasNext()) { + Object nextElement = iterator.next(); + if (nextElement != null) { + return false; + } + } + return true; + } + return false; + } + + private boolean expandCartesianProduct(Pattern pattern, MatchContext matchContext, Map aliasClasses, + Map aliasFilters, Map aliasRids, OCommandContext iCommandContext, + OSQLAsynchQuery request) { + for (String alias : pattern.aliasToNode.keySet()) { + if (!matchContext.matched.containsKey(alias)) { + String target = aliasClasses.get(alias); + if (target == null) { + throw new OCommandExecutionException("Cannot execute MATCH statement on alias " + alias + ": class not defined"); + } + + Iterable values = fetchAliasCandidates(alias, aliasFilters, iCommandContext, aliasClasses, aliasRids); + for (OIdentifiable id : values) { + MatchContext childContext = matchContext.copy(alias, id); + if (allNodesCalculated(childContext, pattern)) { + // false if limit reached + boolean added = addResult(childContext, request, iCommandContext); + if (!added) { + return false; + } + } else { + // false if limit reached + boolean added = expandCartesianProduct(pattern, childContext, aliasClasses, aliasFilters, aliasRids, iCommandContext, + request); + if (!added) { + return false; + } + } + } + break; + } + } + return true; + } + + private boolean allNodesCalculated(MatchContext matchContext, Pattern pattern) { + for (String alias : pattern.aliasToNode.keySet()) { + if (!matchContext.matched.containsKey(alias)) { + return false; + } + } + return true; + } + + private boolean addResult(MatchContext matchContext, OSQLAsynchQuery request, OCommandContext ctx) { + + ODocument doc = null; + if (returnsElements()) { + for (Map.Entry entry : matchContext.matched.entrySet()) { + if (isExplicitAlias(entry.getKey()) && entry.getValue() != null) { + ORecord record = entry.getValue().getRecord(); + if (request.getResultListener() != null && record != null) { + if (!addSingleResult(request, (OBasicCommandContext) ctx, record)) + return false; + } + } + } + } else if (returnsPathElements()) { + for (Map.Entry entry : matchContext.matched.entrySet()) { + if (entry.getValue() != null) { + ORecord record = entry.getValue().getRecord(); + if (request.getResultListener() != null && record != null) { + if (!addSingleResult(request, (OBasicCommandContext) ctx, record)) + return false; + } + } + } + } else if (returnsPatterns()) { + doc = getDatabase().newInstance(); + doc.setTrackingChanges(false); + for (Map.Entry entry : matchContext.matched.entrySet()) { + if (isExplicitAlias(entry.getKey())) { + doc.field(entry.getKey(), entry.getValue()); + } + } + } else if (returnsPaths()) { + doc = getDatabase().newInstance(); + doc.setTrackingChanges(false); + for (Map.Entry entry : matchContext.matched.entrySet()) { + doc.field(entry.getKey(), entry.getValue()); + } + } else if (returnsJson()) { + doc = jsonToDoc(matchContext, ctx); + } else { + doc = getDatabase().newInstance(); + doc.setTrackingChanges(false); + int i = 0; + + ODocument mapDoc = new ODocument(); + mapDoc.setTrackingChanges(false); + mapDoc.fromMap((Map) matchContext.matched); + ctx.setVariable("$current", mapDoc); + for (OExpression item : returnItems) { + OIdentifier returnAliasIdentifier = returnAliases.get(i); + OIdentifier returnAlias; + if (returnAliasIdentifier == null) { + returnAlias = item.getDefaultAlias(); + } else { + returnAlias = returnAliasIdentifier; + } + + doc.field(returnAlias.getStringValue(), item.execute(mapDoc, ctx)); + i++; + } + doc.setTrackingChanges(true); + } + + if (request.getResultListener() != null && doc != null) { + if (!addSingleResult(request, (OBasicCommandContext) ctx, doc)) + return false; + } + + return true; + } + + /** + * @param request + * @param ctx + * @param record + * + * @return false if limit was reached + */ + private boolean addSingleResult(OSQLAsynchQuery request, OBasicCommandContext ctx, ORecord record) { + if (((OBasicCommandContext) context).addToUniqueResult(record)) { + request.getResultListener().result(record); + long currentCount = ctx.getResultsProcessed().incrementAndGet(); + long limitValue = limitFromProtocol; + if (limit != null) { + limitValue = limit.num.getValue().longValue(); + } + if (limitValue > -1 && limitValue <= currentCount) { + return false; + } + } + return true; + } + + private boolean returnsPathElements() { + for (OExpression item : returnItems) { + if (item.toString().equalsIgnoreCase("$pathElements")) { + return true; + } + } + return false; + } + + private boolean returnsElements() { + for (OExpression item : returnItems) { + if (item.toString().equalsIgnoreCase("$elements")) { + return true; + } + } + return false; + } + + private boolean returnsPatterns() { + for (OExpression item : returnItems) { + if (item.toString().equalsIgnoreCase("$patterns")) { + return true; + } + if (item.toString().equalsIgnoreCase("$matches")) { + return true; + } + } + return false; + } + + private boolean returnsPaths() { + for (OExpression item : returnItems) { + if (item.toString().equalsIgnoreCase("$paths")) { + return true; + } + } + return false; + } + + private boolean returnsJson() { + if (returnItems.size() == 1 && (returnItems.get(0).value instanceof OJson) && returnAliases.get(0) == null) { + return true; + } + return false; + } + + private ODocument jsonToDoc(MatchContext matchContext, OCommandContext ctx) { + if (returnItems.size() == 1 && (returnItems.get(0).value instanceof OJson) && returnAliases.get(0) == null) { + ODocument result = new ODocument(); + result.setTrackingChanges(false); + result.fromMap(((OJson) returnItems.get(0).value).toMap(matchContext.toDoc(), ctx)); + return result; + } + throw new IllegalStateException("Match RETURN statement is not a plain JSON"); + } + + private boolean isExplicitAlias(String key) { + if (key.startsWith(DEFAULT_ALIAS_PREFIX)) { + return false; + } + return true; + } + + private Iterator query(final String className, final OWhereClause oWhereClause, final ORID rid, + final OCommandContext ctx) { + final ODatabaseDocument database = getDatabase(); + if (className != null) { + OClass schemaClass = database.getMetadata().getSchema().getClass(className); + database.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, schemaClass.getName().toLowerCase(Locale.ENGLISH)); +// Iterable baseIterable = fetchFromIndex(schemaClass, oWhereClause); + } + + // OSelectStatement stm = buildSelectStatement(className, oWhereClause); + // return stm.execute(ctx); + + String text; + if (oWhereClause == null) { + if (rid != null) { + text = "(select from " + rid + ")"; + } else { + text = "(select from " + className + ")"; + } + } else { + StringBuilder builder = new StringBuilder(); + oWhereClause.toString(ctx.getInputParameters(), builder); + + synchronized (oWhereClause) { //this instance is shared... + replaceIdentifier(oWhereClause, "$currentMatch", "@this"); + text = "(select from " + (rid != null ? rid : className) + " where " + builder.toString() + .replaceAll("\\$currentMatch", "@this") + ")"; + replaceIdentifier(oWhereClause, "@this", "$currentMatch"); + } + } + OSQLTarget target = new OSQLTarget(text, ctx); + Iterable targetResult = (Iterable) target.getTargetRecords(); + if (targetResult == null) { + return null; + } + + if(targetResult instanceof OCommandExecutorSQLSelect){ + ((OCommandExecutorSQLSelect) targetResult).getContext().setRecordingMetrics(ctx.isRecordingMetrics()); + }else if(targetResult instanceof OCommandExecutorSQLResultsetDelegate){ + OCommandExecutor delegate = ((OCommandExecutorSQLResultsetDelegate) targetResult).getDelegate(); + if(delegate instanceof OCommandExecutorSQLSelect){ + delegate.getContext().setRecordingMetrics(ctx.isRecordingMetrics()); + } + } + return targetResult.iterator(); + } + + private void replaceIdentifier(SimpleNode node, String from, String to) { + if (node instanceof OIdentifier) { + if (from.equals(node.getValue())) { + ((OIdentifier) node).setValue(to); + } + } else { + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + replaceIdentifier((SimpleNode) node.jjtGetChild(i), from, to); + } + } + + } + + private OSelectStatement buildSelectStatement(String className, OWhereClause oWhereClause) { + OSelectStatement stm = new OSelectStatement(-1); + stm.whereClause = oWhereClause; + stm.target = new OFromClause(-1); + stm.target.item = new OFromItem(-1); + stm.target.item.identifier = new OBaseIdentifier(-1); + stm.target.item.identifier.suffix = new OSuffixIdentifier(-1); + stm.target.item.identifier.suffix.identifier = new OIdentifier(-1); + stm.target.item.identifier.suffix.identifier.value = className; + return stm; + } + + private Iterable fetchFromIndex(OClass schemaClass, OWhereClause oWhereClause) { + return null;// TODO + } + + private String getNextAlias(Map estimatedRootEntries, MatchContext matchContext) { + Map.Entry lowerValue = null; + for (Map.Entry entry : estimatedRootEntries.entrySet()) { + if (matchContext.matched.containsKey(entry.getKey())) { + continue; + } + if (lowerValue == null) { + lowerValue = entry; + } else if (lowerValue.getValue() > entry.getValue()) { + lowerValue = entry; + } + } + + if (lowerValue == null) { + throw new OCommandExecutionException("Cannot calculate this pattern (maybe a circular dependency on $matched conditions)"); + } + return lowerValue.getKey(); + } + + private Map estimateRootEntries(Map aliasClasses, Map aliasFilters, + Map aliasRids, OCommandContext ctx) { + Set allAliases = new LinkedHashSet(); + allAliases.addAll(aliasClasses.keySet()); + allAliases.addAll(aliasFilters.keySet()); + allAliases.addAll(aliasRids.keySet()); + + ODatabaseDocumentInternal db = getDatabase(); + OSchema schema = db.getMetadata().getSchema(); + + Map result = new LinkedHashMap(); + for (String alias : allAliases) { + if (this.pattern.aliasToNode.get(alias).isOptionalNode()) { + continue; + } + ORID rid = aliasRids.get(alias); + String className = aliasClasses.get(alias); + if (rid != null) { + if (className != null) { + OClass ridClass = db.getMetadata().getSchema().getClassByClusterId(rid.getClusterId()); + if (!ridClass.isSubClassOf(className)) { + result.put(alias, 0l);//RID and class don't match + continue; + } + } + ORecord doc = db.load(rid); + if (doc == null) { + result.put(alias, 0l); + } else { + result.put(alias, 1l); + } + continue; + } + if (className == null) { + continue; + } + + if (!schema.existsClass(className)) { + throw new OCommandExecutionException("class not defined: " + className); + } + OClass oClass = schema.getClass(className); + long upperBound; + OWhereClause filter = aliasFilters.get(alias); + if (filter != null) { + List aliasesOnPattern = filter.baseExpression.getMatchPatternInvolvedAliases(); + if (aliasesOnPattern != null && aliasesOnPattern.size() > 0) { + //skip root nodes that have a condition on $matched, because they have to be calculated as downstream + continue; + } + upperBound = filter.estimate(oClass, this.threshold, ctx); + } else { + upperBound = oClass.count(); + } + result.put(alias, upperBound); + } + return result; + } + + private void addAliases(OMatchExpression expr, Map aliasFilters, Map aliasClasses, + Map aliasRids, OCommandContext context) { + addAliases(expr.origin, aliasFilters, aliasClasses, aliasRids, context); + for (OMatchPathItem item : expr.items) { + if (item.filter != null) { + addAliases(item.filter, aliasFilters, aliasClasses, aliasRids, context); + } + } + } + + private void addAliases(OMatchFilter matchFilter, Map aliasFilters, Map aliasClasses, + Map aliasRids, OCommandContext context) { + String alias = matchFilter.getAlias(); + OWhereClause filter = matchFilter.getFilter(); + if (alias != null) { + if (filter != null && filter.baseExpression != null) { + OWhereClause previousFilter = aliasFilters.get(alias); + if (previousFilter == null) { + previousFilter = new OWhereClause(-1); + previousFilter.baseExpression = new OAndBlock(-1); + aliasFilters.put(alias, previousFilter); + } + OAndBlock filterBlock = (OAndBlock) previousFilter.baseExpression; + if (filter != null && filter.baseExpression != null) { + filterBlock.subBlocks.add(filter.baseExpression); + } + } + + String clazz = matchFilter.getClassName(context); + if (clazz != null) { + String previousClass = aliasClasses.get(alias); + if (previousClass == null) { + aliasClasses.put(alias, clazz); + } else { + String lower = getLowerSubclass(clazz, previousClass); + if (lower == null) { + throw new OCommandExecutionException( + "classes defined for alias " + alias + " (" + clazz + ", " + previousClass + ") are not in the same hierarchy"); + } + aliasClasses.put(alias, lower); + } + } + + ORID rid = matchFilter.getRid(context); + if (rid != null) { + aliasRids.put(alias, rid); + } + } + } + + private String getLowerSubclass(String className1, String className2) { + OSchema schema = getDatabase().getMetadata().getSchema(); + OClass class1 = schema.getClass(className1); + OClass class2 = schema.getClass(className2); + if (class1 == null) { + throw new OCommandExecutionException("Class " + className1 + " not found in the schema"); + } + if (class2 == null) { + throw new OCommandExecutionException("Class " + className2 + " not found in the schema"); + } + if (class1.isSubClassOf(class2)) { + return class1.getName(); + } + if (class2.isSubClassOf(class1)) { + return class2.getName(); + } + return null; + } + + @Override + public RET setProgressListener(OProgressListener progressListener) { + this.progressListener = progressListener; + return (RET) this; + } + + @Override + public RET setLimit(int iLimit) { + limitFromProtocol = iLimit; + return (RET) this; + } + + @Override + public String getFetchPlan() { + return null; + } + + @Override + public Map getParameters() { + return null; + } + + @Override + public OCommandContext getContext() { + return context; + } + + @Override + public void setContext(OCommandContext context) { + this.context = context; + } + + @Override + public boolean isIdempotent() { + return true; + } + + @Override + public Set getInvolvedClusters() { + return Collections.EMPTY_SET; + } + + @Override + public int getSecurityOperationType() { + return ORole.PERMISSION_READ; + } + + @Override + public boolean involveSchema() { + return false; + } + + @Override + public String getSyntax() { + return "MATCH [, [, ]"; + } + + @Override + public boolean isLocalExecution() { + return true; + } + + @Override + public boolean isCacheable() { + return false; + } + + @Override + public long getDistributedTimeout() { + return -1; + } + + @Override + public Object mergeResults(Map results) throws Exception { + return results; + } + + public void toString(Map params, StringBuilder builder) { + builder.append(KEYWORD_MATCH); + builder.append(" "); + boolean first = true; + for (OMatchExpression expr : this.matchExpressions) { + if (!first) { + builder.append(", "); + } + expr.toString(params, builder); + first = false; + } + builder.append(" RETURN "); + first = true; + int i = 0; + for (OExpression expr : this.returnItems) { + if (!first) { + builder.append(", "); + } + expr.toString(params, builder); + if (returnAliases != null && i < returnAliases.size() && returnAliases.get(i) != null) { + builder.append(" AS "); + returnAliases.get(i).toString(params, builder); + } + i++; + first = false; + } + if (limit != null) { + limit.toString(params, builder); + } + } + + @Override + public Iterator iterator(Map iArgs) { + if (context == null) { + context = new OBasicCommandContext(); + } + Object result = execute(iArgs); + return ((Iterable) result).iterator(); + } +} +/* JavaCC - OriginalChecksum=6ff0afbe9d31f08b72159fcf24070c9f (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchesCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchesCondition.java new file mode 100644 index 00000000000..ec7a2b3cf73 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMatchesCondition.java @@ -0,0 +1,71 @@ +/* Generated By:JJTree: Do not edit this line. OMatchesCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OMatchesCondition extends OBooleanExpression { + protected OExpression expression; + protected String right; + protected OInputParameter rightParam; + + public OMatchesCondition(int id) { + super(id); + } + + public OMatchesCondition(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + + public void toString(Map params, StringBuilder builder) { + expression.toString(params, builder); + builder.append(" MATCHES "); + if(right!=null) { + builder.append(right); + }else{ + rightParam.toString(params, builder); + } + } + + @Override + public boolean supportsBasicCalculation() { + return expression.supportsBasicCalculation(); + } + + @Override + protected int getNumberOfExternalCalculations() { + if (expression != null && !expression.supportsBasicCalculation()) { + return 1; + } + return 0; + } + + @Override + protected List getExternalCalculationConditions() { + if (expression != null && !expression.supportsBasicCalculation()) { + return (List) Collections.singletonList(expression); + } + return Collections.EMPTY_LIST; + } + + @Override public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases(); + } + +} +/* JavaCC - OriginalChecksum=68712f476e2e633c2bbfc34cb6c39356 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMathExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMathExpression.java new file mode 100644 index 00000000000..e5f5f049b55 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMathExpression.java @@ -0,0 +1,351 @@ +/* Generated By:JJTree: Do not edit this line. OMathExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OMathExpression extends SimpleNode { + + public enum Operator { + PLUS { + @Override public Number apply(Integer left, Integer right) { + final Integer sum = left + right; + if (sum < 0 && left.intValue() > 0 && right.intValue() > 0) + // SPECIAL CASE: UPGRADE TO LONG + return left.longValue() + right; + return sum; + } + + @Override public Number apply(Long left, Long right) { + return left + right; + } + + @Override public Number apply(Float left, Float right) { + return left + right; + } + + @Override public Number apply(Double left, Double right) { + return left + right; + } + + @Override public Number apply(BigDecimal left, BigDecimal right) { + return left.add(right); + } + }, + MINUS { + @Override public Number apply(Integer left, Integer right) { + int result = left - right; + if (result > 0 && left.intValue() < 0 && right.intValue() > 0) + // SPECIAL CASE: UPGRADE TO LONG + return left.longValue() - right; + + return result; + } + + @Override public Number apply(Long left, Long right) { + return left - right; + } + + @Override public Number apply(Float left, Float right) { + return left - right; + } + + @Override public Number apply(Double left, Double right) { + return left - right; + } + + @Override public Number apply(BigDecimal left, BigDecimal right) { + return left.subtract(right); + } + }, + STAR { + @Override public Number apply(Integer left, Integer right) { + return left * right; + } + + @Override public Number apply(Long left, Long right) { + return left * right; + } + + @Override public Number apply(Float left, Float right) { + return left * right; + } + + @Override public Number apply(Double left, Double right) { + return left * right; + } + + @Override public Number apply(BigDecimal left, BigDecimal right) { + return left.multiply(right); + } + }, + SLASH { + @Override public Number apply(Integer left, Integer right) { + return left / right; + } + + @Override public Number apply(Long left, Long right) { + return left / right; + } + + @Override public Number apply(Float left, Float right) { + return left / right; + } + + @Override public Number apply(Double left, Double right) { + return left / right; + } + + @Override public Number apply(BigDecimal left, BigDecimal right) { + return left.divide(right, BigDecimal.ROUND_HALF_UP); + } + }, + REM { + @Override public Number apply(Integer left, Integer right) { + return left % right; + } + + @Override public Number apply(Long left, Long right) { + return left % right; + } + + @Override public Number apply(Float left, Float right) { + return left % right; + } + + @Override public Number apply(Double left, Double right) { + return left % right; + } + + @Override public Number apply(BigDecimal left, BigDecimal right) { + return left.remainder(right); + } + }; + + public abstract Number apply(Integer left, Integer right); + + public abstract Number apply(Long left, Long right); + + public abstract Number apply(Float left, Float right); + + public abstract Number apply(Double left, Double right); + + public abstract Number apply(BigDecimal left, BigDecimal right); + + } + + protected List childExpressions = new ArrayList(); + protected List operators = new ArrayList(); + + public OMathExpression(int id) { + super(id); + } + + public OMathExpression(OrientSql p, int id) { + super(p, id); + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (childExpressions.size() == 0) { + return null; + } + + OMathExpression nextExpression = childExpressions.get(0); + Object nextValue = nextExpression.execute(iCurrentRecord, ctx); + for (int i = 0; i < operators.size() && i + 1 < childExpressions.size(); i++) { + Operator nextOperator = operators.get(i); + Object rightValue = childExpressions.get(i + 1).execute(iCurrentRecord, ctx); + nextValue = apply(nextValue, nextOperator, rightValue); + } + return nextValue; + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public List getChildExpressions() { + return childExpressions; + } + + public void setChildExpressions(List childExpressions) { + this.childExpressions = childExpressions; + } + + public void toString(Map params, StringBuilder builder) { + for (int i = 0; i < childExpressions.size(); i++) { + if (i > 0) { + builder.append(" "); + switch (operators.get(i - 1)) { + case PLUS: + builder.append("+"); + break; + case MINUS: + builder.append("-"); + break; + case STAR: + builder.append("*"); + break; + case SLASH: + builder.append("/"); + break; + case REM: + builder.append("%"); + break; + } + builder.append(" "); + } + childExpressions.get(i).toString(params, builder); + } + } + + public Object apply(final Object a, final Operator operation, final Object b) { + if (b == null) { + return a; + } + if (a == null) { + return b; + } + if (a instanceof Number && b instanceof Number) { + return apply((Number) a, operation, (Number) b); + } + if (a instanceof String || b instanceof String) { + return "" + a + b; + } + throw new IllegalArgumentException( + "Cannot apply operaton " + operation + " to value '" + a + "' (" + a.getClass() + ") with '" + b + "' (" + b.getClass() + + ")"); + + } + + public Number apply(final Number a, final Operator operation, final Number b) { + if (a == null || b == null) + throw new IllegalArgumentException("Cannot increment a null value"); + + if (a instanceof Integer || a instanceof Short) { + if (b instanceof Integer || b instanceof Short) { + return operation.apply(a.intValue(), b.intValue()); + } else if (b instanceof Long) { + return operation.apply(a.longValue(), b.longValue()); + } else if (b instanceof Float) + return operation.apply(a.floatValue(), b.floatValue()); + else if (b instanceof Double) + return operation.apply(a.doubleValue(), b.doubleValue()); + else if (b instanceof BigDecimal) + return operation.apply(new BigDecimal((Integer) a), (BigDecimal) b); + } else if (a instanceof Long) { + if (b instanceof Integer || b instanceof Long || b instanceof Short) + return operation.apply(a.longValue(), b.longValue()); + else if (b instanceof Float) + return operation.apply(a.floatValue(), b.floatValue()); + else if (b instanceof Double) + return operation.apply(a.doubleValue(), b.doubleValue()); + else if (b instanceof BigDecimal) + return operation.apply(new BigDecimal((Long) a), (BigDecimal) b); + } else if (a instanceof Float) { + if (b instanceof Short || b instanceof Integer || b instanceof Long || b instanceof Float) + return operation.apply(a.floatValue(), b.floatValue()); + else if (b instanceof Double) + return operation.apply(a.doubleValue(), b.doubleValue()); + else if (b instanceof BigDecimal) + return operation.apply(new BigDecimal((Float) a), (BigDecimal) b); + + } else if (a instanceof Double) { + if (b instanceof Short || b instanceof Integer || b instanceof Long || b instanceof Float || b instanceof Double) + return operation.apply(a.doubleValue(), b.doubleValue()); + else if (b instanceof BigDecimal) + return operation.apply(new BigDecimal((Double) a), (BigDecimal) b); + + } else if (a instanceof BigDecimal) { + if (b instanceof Integer) + return operation.apply((BigDecimal) a, new BigDecimal((Integer) b)); + else if (b instanceof Long) + return operation.apply((BigDecimal) a, new BigDecimal((Long) b)); + else if (b instanceof Short) + return operation.apply((BigDecimal) a, new BigDecimal((Short) b)); + else if (b instanceof Float) + return operation.apply((BigDecimal) a, new BigDecimal((Float) b)); + else if (b instanceof Double) + return operation.apply((BigDecimal) a, new BigDecimal((Double) b)); + else if (b instanceof BigDecimal) + return operation.apply((BigDecimal) a, (BigDecimal) b); + } + + throw new IllegalArgumentException( + "Cannot increment value '" + a + "' (" + a.getClass() + ") with '" + b + "' (" + b.getClass() + ")"); + + } + + protected boolean supportsBasicCalculation() { + for (OMathExpression expr : this.childExpressions) { + if (!expr.supportsBasicCalculation()) { + return false; + } + } + return true; + + } + + public boolean isIndexedFunctionCall() { + if (this.childExpressions.size() != 1) { + return false; + } + return this.childExpressions.get(0).isIndexedFunctionCall(); + } + + public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) { + if (this.childExpressions.size() != 1) { + return -1; + } + return this.childExpressions.get(0).estimateIndexedFunction(target, context, operator, right); + } + + public Iterable executeIndexedFunction(OFromClause target, OCommandContext context, + OBinaryCompareOperator operator, Object right) { + if (this.childExpressions.size() != 1) { + return null; + } + return this.childExpressions.get(0).executeIndexedFunction(target, context, operator, right); + } + + public boolean isBaseIdentifier() { + if (childExpressions.size() == 1) { + return childExpressions.get(0).isBaseIdentifier(); + } + return false; + } + + public boolean isEarlyCalculated() { + for (OMathExpression exp : childExpressions) { + if (!exp.isEarlyCalculated()) { + return false; + } + } + return true; + } + + public List getMatchPatternInvolvedAliases() { + List result = new ArrayList(); + for (OMathExpression exp : childExpressions) { + List x = exp.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + } + if (result.size() == 0) { + return null; + } + return result; + } + +} +/* JavaCC - OriginalChecksum=c255bea24e12493e1005ba2a4d1dbb9d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMetadataIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMetadataIdentifier.java new file mode 100644 index 00000000000..c553e20012d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMetadataIdentifier.java @@ -0,0 +1,29 @@ +/* Generated By:JJTree: Do not edit this line. OMetadataIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OMetadataIdentifier extends SimpleNode { + + protected String name; + + public OMetadataIdentifier(int id) { + super(id); + } + + public OMetadataIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("metadata:"); + builder.append(name); + } +} +/* JavaCC - OriginalChecksum=85e179b9505270f0596904070fdf0745 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMethodCall.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMethodCall.java new file mode 100644 index 00000000000..7972cfe17f9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMethodCall.java @@ -0,0 +1,133 @@ +/* Generated By:JJTree: Do not edit this line. OMethodCall.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.sql.OSQLEngine; +import com.orientechnologies.orient.core.sql.functions.OSQLFunction; +import com.orientechnologies.orient.core.sql.functions.OSQLFunctionFiltered; +import com.orientechnologies.orient.core.sql.method.OSQLMethod; + +import java.util.*; + +public class OMethodCall extends SimpleNode { + + static Set graphMethods = new HashSet(Arrays.asList(new String[] { "out", "in", "both", "outE", + "inE", "bothE", "bothV", "outV", "inV" })); + + static Set bidirectionalMethods = new HashSet(Arrays.asList(new String[] { "out", "in", "both", "oute", "ine", "inv", "outv" })); + + protected OIdentifier methodName; + protected List params = new ArrayList(); + + public OMethodCall(int id) { + super(id); + } + + public OMethodCall(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("."); + methodName.toString(params, builder); + builder.append("("); + boolean first = true; + for (OExpression param : this.params) { + if (!first) { + builder.append(", "); + } + param.toString(params, builder); + first = false; + } + builder.append(")"); + } + + public boolean isBidirectional() { + return bidirectionalMethods.contains(methodName.getStringValue().toLowerCase(Locale.ENGLISH)); + } + + public Object execute(Object targetObjects, OCommandContext ctx) { + return execute(targetObjects, ctx, methodName.getStringValue(), params, null); + } + + public Object execute(Object targetObjects, Iterable iPossibleResults, OCommandContext ctx) { + return execute(targetObjects, ctx, methodName.getStringValue(), params, iPossibleResults); + } + + private Object execute(Object targetObjects, OCommandContext ctx, String name, List iParams, + Iterable iPossibleResults) { + List paramValues = new ArrayList(); + for (OExpression expr : iParams) { + paramValues.add(expr.execute((OIdentifiable) ctx.getVariable("$current"), ctx)); + } + if (graphMethods.contains(name)) { + OSQLFunction function = OSQLEngine.getInstance().getFunction(name); + if (function instanceof OSQLFunctionFiltered) { + return ((OSQLFunctionFiltered) function).execute(targetObjects, (OIdentifiable) ctx.getVariable("$current"), null, + paramValues.toArray(), iPossibleResults, ctx); + } else { + return function.execute(targetObjects, (OIdentifiable) ctx.getVariable("$current"), null, paramValues.toArray(), ctx); + } + + } + OSQLMethod method = OSQLEngine.getMethod(name); + if (method != null) { + return method.execute(targetObjects, (OIdentifiable) ctx.getVariable("$current"), ctx, targetObjects, paramValues.toArray()); + } + throw new UnsupportedOperationException("OMethod call, something missing in the implementation...?"); + + } + + public Object executeReverse(Object targetObjects, OCommandContext ctx) { + if (!isBidirectional()) { + throw new UnsupportedOperationException(); + } + + String straightName = methodName.getStringValue(); + if (straightName.equalsIgnoreCase("out")) { + return execute(targetObjects, ctx, "in", params, null); + } + if (straightName.equalsIgnoreCase("in")) { + return execute(targetObjects, ctx, "out", params, null); + } + + if (straightName.equalsIgnoreCase("both")) { + return execute(targetObjects, ctx, "both", params, null); + } + + if (straightName.equalsIgnoreCase("outE")) { + return execute(targetObjects, ctx, "outV", params, null); + } + + if (straightName.equalsIgnoreCase("outV")) { + return execute(targetObjects, ctx, "outE", params, null); + } + + if (straightName.equalsIgnoreCase("inE")) { + return execute(targetObjects, ctx, "inV", params, null); + } + + if (straightName.equalsIgnoreCase("inV")) { + return execute(targetObjects, ctx, "inE", params, null); + } + + throw new UnsupportedOperationException("Invalid reverse traversal: " + methodName); + } + + public static ODatabaseDocumentInternal getDatabase() { + return ODatabaseRecordThreadLocal.INSTANCE.get(); + } + +} +/* JavaCC - OriginalChecksum=da95662da21ceb8dee3ad88c0d980413 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OModifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OModifier.java new file mode 100644 index 00000000000..8c93953d17a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OModifier.java @@ -0,0 +1,109 @@ +/* Generated By:JJTree: Do not edit this line. OModifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class OModifier extends SimpleNode { + + boolean squareBrackets = false; + OArrayRangeSelector arrayRange; + OOrBlock condition; + OArraySingleValuesSelector arraySingleValues; + OMethodCall methodCall; + OSuffixIdentifier suffix; + + OModifier next; + + public OModifier(int id) { + super(id); + } + + public OModifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + + if (squareBrackets) { + builder.append("["); + + if (arrayRange != null) { + arrayRange.toString(params, builder); + } else if (condition != null) { + condition.toString(params, builder); + } else if (arraySingleValues != null) { + arraySingleValues.toString(params, builder); + } + + builder.append("]"); + } else if (methodCall != null) { + methodCall.toString(params, builder); + } else if (suffix != null) { + builder.append("."); + suffix.toString(params, builder); + } + if (next != null) { + next.toString(params, builder); + } + } + + public Object execute(OIdentifiable iCurrentRecord, Object result, OCommandContext ctx) { + if (methodCall != null) { + result = methodCall.execute(result, ctx); + } else if (suffix != null) { + result = suffix.execute(result, ctx); + } else if (arrayRange != null) { + result = arrayRange.execute(iCurrentRecord, result, ctx); + } else if (condition != null) { + result = filterByCondition(iCurrentRecord, result, ctx); + } else if (arraySingleValues != null) { + result = arraySingleValues.execute(iCurrentRecord, result, ctx); + } + if (next != null) { + result = next.execute(iCurrentRecord, result, ctx); + } + return result; + } + + private Object filterByCondition(OIdentifiable iCurrentRecord, Object iResult, OCommandContext ctx) { + if(iResult==null){ + return null; + } + List result = new ArrayList(); + if(iResult.getClass().isArray()){ + for(int i=0;i< Array.getLength(iResult); i++){ + Object item = Array.get(iResult, i); + if(condition.evaluate(item, ctx)){ + result.add(item); + } + } + return result; + } + if(iResult instanceof Iterable){ + iResult = ((Iterable) iResult).iterator(); + } + if(iResult instanceof Iterator){ + while(((Iterator) iResult).hasNext()){ + Object item = ((Iterator) iResult).next(); + if(condition.evaluate(item, ctx)){ + result.add(item); + } + } + } + return result; + } +} +/* JavaCC - OriginalChecksum=39c21495d02f9b5007b4a2d6915496e1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMoveVertexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMoveVertexStatement.java new file mode 100644 index 00000000000..bcfa4eb1fd7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMoveVertexStatement.java @@ -0,0 +1,115 @@ +/* Generated By:JJTree: Do not edit this line. OMoveVertexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OMoveVertexStatement extends OStatement { + protected OFromItem source; + protected OCluster targetCluster; + protected OIdentifier targetClass; + protected OUpdateOperations updateOperations; + protected OBatch batch; + + public OMoveVertexStatement(int id) { + super(id); + } + + public OMoveVertexStatement(OrientSql p, int id) { + super(p, id); + } + + + public void toString(Map params, StringBuilder builder) { + builder.append("MOVE VERTEX "); + source.toString(params, builder); + builder.append(" TO "); + if (targetCluster != null) { + targetCluster.toString(params, builder); + } else { + builder.append("CLASS:"); + targetClass.toString(params, builder); + } + + if (updateOperations != null) { + builder.append(" "); + updateOperations.toString(params, builder); + } + + if (batch != null) { + builder.append(" "); + batch.toString(params, builder); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + OMoveVertexStatement that = (OMoveVertexStatement) o; + + if (!source.equals(that.source)) + return false; + if (targetCluster != null ? !targetCluster.equals(that.targetCluster) : that.targetCluster != null) + return false; + if (targetClass != null ? !targetClass.equals(that.targetClass) : that.targetClass != null) + return false; + if (updateOperations != null ? !updateOperations.equals(that.updateOperations) : that.updateOperations != null) + return false; + return batch != null ? batch.equals(that.batch) : that.batch == null; + } + + @Override + public int hashCode() { + int result = source.hashCode(); + result = 31 * result + (targetCluster != null ? targetCluster.hashCode() : 0); + result = 31 * result + (targetClass != null ? targetClass.hashCode() : 0); + result = 31 * result + (updateOperations != null ? updateOperations.hashCode() : 0); + result = 31 * result + (batch != null ? batch.hashCode() : 0); + return result; + } + + public OFromItem getSource() { + return source; + } + + public void setSource(OFromItem source) { + this.source = source; + } + + public OCluster getTargetCluster() { + return targetCluster; + } + + public void setTargetCluster(OCluster targetCluster) { + this.targetCluster = targetCluster; + } + + public OIdentifier getTargetClass() { + return targetClass; + } + + public void setTargetClass(OIdentifier targetClass) { + this.targetClass = targetClass; + } + + public OUpdateOperations getUpdateOperations() { + return updateOperations; + } + + public void setUpdateOperations(OUpdateOperations updateOperations) { + this.updateOperations = updateOperations; + } + + public OBatch getBatch() { + return batch; + } + + public void setBatch(OBatch batch) { + this.batch = batch; + } +} +/* JavaCC - OriginalChecksum=5cb0b9d3644fd28813ff615fe59d577d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultExpression.java new file mode 100644 index 00000000000..7a61e21f4cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultExpression.java @@ -0,0 +1,22 @@ +/* Generated By:JJTree: Do not edit this line. OMultExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OMultExpression extends OMathExpression { + + public OMultExpression(int id) { + super(id); + } + + public OMultExpression(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=f75b8be48dca1e0cafae0cacadc608c8 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItem.java new file mode 100644 index 00000000000..c3485cfc95b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItem.java @@ -0,0 +1,62 @@ +/* Generated By:JJTree: Do not edit this line. OMultiMatchPathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.*; + +public class OMultiMatchPathItem extends OMatchPathItem { + protected List items = new ArrayList(); + + public OMultiMatchPathItem(int id) { + super(id); + } + + public OMultiMatchPathItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean isBidirectional() { + return false; + } + + public void toString(Map params, StringBuilder builder) { + builder.append(".("); + for (OMatchPathItem item : items) { + item.toString(params, builder); + } + builder.append(")"); + if (filter != null) { + filter.toString(params, builder); + } + } + + protected Iterable traversePatternEdge(OMatchStatement.MatchContext matchContext, OIdentifiable startingPoint, + OCommandContext iCommandContext) { + Set result = new HashSet(); + result.add(startingPoint); + for (OMatchPathItem subItem : items) { + Set startingPoints = result; + result = new HashSet(); + for (OIdentifiable sp : startingPoints) { + Iterable subResult = subItem.executeTraversal(matchContext, iCommandContext, sp, 0); + if (subResult instanceof Collection) { + result.addAll((Collection) subResult); + } else { + for (OIdentifiable id : subResult) { + result.add(id); + } + } + } + } + return result; + } +} +/* JavaCC - OriginalChecksum=f18f107768de80b8941f166d7fafb3c0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItemArrows.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItemArrows.java new file mode 100644 index 00000000000..110070692d9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OMultiMatchPathItemArrows.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OMultiMatchPathItemArrows.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OMultiMatchPathItemArrows extends OMultiMatchPathItem { + public OMultiMatchPathItemArrows(int id) { + super(id); + } + + public OMultiMatchPathItemArrows(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=75506ca75aab9f66ab24c9f1b1cfe3ac (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONamedParameter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONamedParameter.java new file mode 100644 index 00000000000..6f85833f48b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONamedParameter.java @@ -0,0 +1,57 @@ +/* Generated By:JJTree: Do not edit this line. ONamedParameter.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ONamedParameter extends OInputParameter { + + protected int paramNumber; + protected String paramName; + + public ONamedParameter(int id) { + super(id); + } + + public ONamedParameter(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public String toString() { + return ":" + paramName; + } + + public void toString(Map params, StringBuilder builder) { + Object finalValue = bindFromInputParams(params); + if (finalValue == this) { + builder.append(":" + paramName); + } else if (finalValue instanceof String) { + builder.append("\""); + builder.append(OExpression.encode(finalValue.toString())); + builder.append("\""); + } else if (finalValue instanceof SimpleNode) { + ((SimpleNode) finalValue).toString(params, builder); + } else { + builder.append(finalValue); + } + } + + public Object bindFromInputParams(Map params) { + if (params != null) { + String key = paramName; + if (params.containsKey(key)) { + return toParsedTree(params.get(key)); + } + return toParsedTree(params.get(paramNumber)); + } + return this; + } + +} +/* JavaCC - OriginalChecksum=8a00a9cf51a15dd75202f6372257fc1c (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeOperator.java new file mode 100644 index 00000000000..cb21ed7a0eb --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeOperator.java @@ -0,0 +1,37 @@ +/* Generated By:JJTree: Do not edit this line. ONeOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals; + +public +class ONeOperator extends SimpleNode implements OBinaryCompareOperator{ + public ONeOperator(int id) { + super(id); + } + + public ONeOperator(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean execute(Object left, Object right) { + return !OQueryOperatorEquals.equals(left, right); + } + + @Override public String toString() { + return "!="; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=ac0ae426fb86c930dea83013ddc202ba (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONearOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONearOperator.java new file mode 100644 index 00000000000..aa9dc66a329 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONearOperator.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. ONearOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class ONearOperator extends SimpleNode implements OBinaryCompareOperator { + public ONearOperator(int id) { + super(id); + } + + public ONearOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + throw new UnsupportedOperationException(toString() + " operator cannot be evaluated in this context"); + } + + @Override + public String toString() { + return "NEAR"; + } + + @Override public boolean supportsBasicCalculation() { + return false; + } + + +} +/* JavaCC - OriginalChecksum=a79af9beed70f813658f38a0162320e0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeqOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeqOperator.java new file mode 100644 index 00000000000..d080d9aa5f0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONeqOperator.java @@ -0,0 +1,37 @@ +/* Generated By:JJTree: Do not edit this line. ONeqOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals; + +public class ONeqOperator extends SimpleNode implements OBinaryCompareOperator { + public ONeqOperator(int id) { + super(id); + } + + public ONeqOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + return !OQueryOperatorEquals.equals(left, right); + } + + @Override + public String toString() { + return "<>"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=588c4112ae7d2c83239f97ab0d2d5989 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotBlock.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotBlock.java new file mode 100644 index 00000000000..739b812eff5 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotBlock.java @@ -0,0 +1,97 @@ +/* Generated By:JJTree: Do not edit this line. ONotBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; + +import java.util.List; +import java.util.Map; + +public class ONotBlock extends OBooleanExpression { + protected OBooleanExpression sub; + + protected boolean negate = false; + + public ONotBlock(int id) { + super(id); + } + + public ONotBlock(OrientSql p, int id) { + super(p, id); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + if (sub == null) { + return true; + } + boolean result = sub.evaluate(currentRecord, ctx); + if (negate) { + return !result; + } + return result; + } + + public OBooleanExpression getSub() { + return sub; + } + + public void setSub(OBooleanExpression sub) { + this.sub = sub; + } + + public boolean isNegate() { + return negate; + } + + public void setNegate(boolean negate) { + this.negate = negate; + } + + public void toString(Map params, StringBuilder builder) { + if (negate) { + builder.append("NOT "); + } + sub.toString(params, builder); + } + + @Override + public boolean supportsBasicCalculation() { + return true; + } + + @Override + protected int getNumberOfExternalCalculations() { + return sub.getNumberOfExternalCalculations(); + } + + @Override + protected List getExternalCalculationConditions() { + return sub.getExternalCalculationConditions(); + } + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (sub == null) { + return null; + } + if (negate) { + return null; + } + return sub.getIndexedFunctionConditions(iSchemaClass, database); + } + + @Override public List flatten() { + if(!negate){ + return sub.flatten(); + } + return super.flatten(); + } + + @Override public List getMatchPatternInvolvedAliases() { + return sub.getMatchPatternInvolvedAliases(); + } +} +/* JavaCC - OriginalChecksum=1926313b3f854235aaa20811c22d583b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotInCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotInCondition.java new file mode 100644 index 00000000000..9c4924a17a7 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONotInCondition.java @@ -0,0 +1,124 @@ +/* Generated By:JJTree: Do not edit this line. ONotInCondition.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ONotInCondition extends OBooleanExpression { + + protected OExpression left; + protected OBinaryCompareOperator operator; + protected OSelectStatement rightStatement; + + protected Object right; + protected OInputParameter rightParam; + protected OMathExpression rightMathExpression; + + private static final Object UNSET = new Object(); + private Object inputFinalValue = UNSET; + + public ONotInCondition(int id) { + super(id); + } + + public ONotInCondition(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return false; + } + + public void toString(Map params, StringBuilder builder) { + + left.toString(params, builder); + builder.append(" NOT IN "); + if (rightStatement != null) { + builder.append("("); + rightStatement.toString(params, builder); + builder.append(")"); + } else if (right != null) { + builder.append(convertToString(right)); + } else if (rightParam != null) { + rightParam.toString(params, builder); + } else if (rightMathExpression != null) { + rightMathExpression.toString(params, builder); + } + } + + private String convertToString(Object o) { + if (o instanceof String) { + return "\"" + ((String) o).replaceAll("\"", "\\\"") + "\""; + } + return o.toString(); + } + + @Override public boolean supportsBasicCalculation() { + + if (operator != null && !operator.supportsBasicCalculation()) { + return false; + } + if (left != null && !left.supportsBasicCalculation()) { + return false; + } + if (rightMathExpression != null && !rightMathExpression.supportsBasicCalculation()) { + return false; + } + return true; + + } + + @Override protected int getNumberOfExternalCalculations() { + int total = 0; + if (operator != null && !operator.supportsBasicCalculation()) { + total++; + } + if (left != null && !left.supportsBasicCalculation()) { + total++; + } + if (rightMathExpression != null && !rightMathExpression.supportsBasicCalculation()) { + total++; + } + return total; + } + + @Override protected List getExternalCalculationConditions() { + List result = new ArrayList(); + if (operator != null && !operator.supportsBasicCalculation()) { + result.add(this); + } + if (rightMathExpression != null && !rightMathExpression.supportsBasicCalculation()) { + result.add(rightMathExpression); + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List leftX = left == null ? null : left.getMatchPatternInvolvedAliases(); + List rightX = rightMathExpression == null ? null : rightMathExpression.getMatchPatternInvolvedAliases(); + + List result = new ArrayList(); + if (leftX != null) { + result.addAll(leftX); + } + if (rightX != null) { + result.addAll(rightX); + } + + return result.size() == 0 ? null : result; + } + +} +/* JavaCC - OriginalChecksum=8fb82bf72cc7d9cbdf2f9e2323ca8ee1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONumber.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONumber.java new file mode 100644 index 00000000000..ac2737917e3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ONumber.java @@ -0,0 +1,31 @@ +/* Generated By:JJTree: Do not edit this line. ONumber.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ONumber extends SimpleNode { + public ONumber(int id) { + super(id); + } + + public ONumber(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public Number getValue() { + return null; + } + + public void toString(Map params, StringBuilder builder) { + builder.append(value); + } +} +/* JavaCC - OriginalChecksum=ebedbca280f59eb8ba8f21dc6132ba10 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOptimizeDatabaseStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOptimizeDatabaseStatement.java new file mode 100644 index 00000000000..1ec24923f8d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOptimizeDatabaseStatement.java @@ -0,0 +1,29 @@ +/* Generated By:JJTree: Do not edit this line. OOptimizeDatabaseStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OOptimizeDatabaseStatement extends OStatement { + + protected List options = new ArrayList(); + + public OOptimizeDatabaseStatement(int id) { + super(id); + } + + public OOptimizeDatabaseStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("OPTIMIZE DATABASE"); + for (OCommandLineOption option : options) { + builder.append(" "); + option.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=b85d66f84bbae92224565361df9d0c91 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrBlock.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrBlock.java new file mode 100644 index 00000000000..35e9c552241 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrBlock.java @@ -0,0 +1,143 @@ +/* Generated By:JJTree: Do not edit this line. OOrBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OOrBlock extends OBooleanExpression { + List subBlocks = new ArrayList(); + + public OOrBlock(int id) { + super(id); + } + + public OOrBlock(OrientSql p, int id) { + super(p, id); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + if (getSubBlocks() == null) { + return true; + } + + for (OBooleanExpression block : subBlocks) { + if (block.evaluate(currentRecord, ctx)) { + return true; + } + } + return false; + } + + public boolean evaluate(Object currentRecord, OCommandContext ctx) { + if (currentRecord instanceof OIdentifiable) { + return evaluate((OIdentifiable) currentRecord, ctx); + } else if (currentRecord instanceof Map) { + ODocument doc = new ODocument(); + doc.fromMap((Map) currentRecord); + return evaluate(doc, ctx); + } + return false; + } + + public List getSubBlocks() { + return subBlocks; + } + + public void setSubBlocks(List subBlocks) { + this.subBlocks = subBlocks; + } + + public void toString(Map params, StringBuilder builder) { + if (subBlocks == null || subBlocks.size() == 0) { + return; + } + // if (subBlocks.size() == 1) { + // subBlocks.get(0).toString(params, builder); + // return; + // } + + boolean first = true; + for (OBooleanExpression expr : subBlocks) { + if (!first) { + builder.append(" OR "); + } + expr.toString(params, builder); + first = false; + } + } + + @Override + protected boolean supportsBasicCalculation() { + for (OBooleanExpression expr : subBlocks) { + if (!expr.supportsBasicCalculation()) { + return false; + } + } + return true; + } + + @Override + protected int getNumberOfExternalCalculations() { + int result = 0; + for (OBooleanExpression expr : subBlocks) { + result += expr.getNumberOfExternalCalculations(); + } + return result; + } + + @Override + protected List getExternalCalculationConditions() { + List result = new ArrayList(); + for (OBooleanExpression expr : subBlocks) { + result.addAll(expr.getExternalCalculationConditions()); + } + return result; + } + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (subBlocks == null || subBlocks.size() > 1) { + return null; + } + List result = new ArrayList(); + for (OBooleanExpression exp : subBlocks) { + List sub = exp.getIndexedFunctionConditions(iSchemaClass, database); + if (sub != null && sub.size() > 0) { + result.addAll(sub); + } + } + return result.size() == 0 ? null : result; + } + + public List flatten() { + List result = new ArrayList(); + for(OBooleanExpression sub:subBlocks){ + List childFlattened = sub.flatten(); + for(OAndBlock child:childFlattened){ + result.add(child); + } + } + return result; + } + + @Override public List getMatchPatternInvolvedAliases() { + List result = new ArrayList(); + for (OBooleanExpression exp : subBlocks) { + List x = exp.getMatchPatternInvolvedAliases(); + if (x != null) { + result.addAll(x); + } + } + return result.size() == 0 ? null : result; + } + +} +/* JavaCC - OriginalChecksum=98d3077303a598705894dbb7bd4e1573 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderBy.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderBy.java new file mode 100644 index 00000000000..cff0167d768 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderBy.java @@ -0,0 +1,48 @@ +/* Generated By:JJTree: Do not edit this line. OOrderBy.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OOrderBy extends SimpleNode { + protected List items; + + public OOrderBy() { + super(-1); + } + + public OOrderBy(int id) { + super(id); + } + + public OOrderBy(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void toString(Map params, StringBuilder builder) { + if (items != null && items.size() > 0) { + builder.append("ORDER BY "); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + builder.append(", "); + } + items.get(i).toString(params, builder); + } + } + } +} +/* JavaCC - OriginalChecksum=d5529400217169f15e556e5dc6fe4f5b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderByItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderByItem.java new file mode 100644 index 00000000000..512be233f1f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrderByItem.java @@ -0,0 +1,65 @@ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +/** + * Created by luigidellaquila on 06/02/15. + */ +public class OOrderByItem { + public static final String ASC = "ASC"; + public static final String DESC = "DESC"; + protected String alias; + protected OModifier modifier; + protected String recordAttr; + protected ORid rid; + protected String type = ASC; + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRecordAttr() { + return recordAttr; + } + + public void setRecordAttr(String recordAttr) { + this.recordAttr = recordAttr; + } + + public ORid getRid() { + return rid; + } + + public void setRid(ORid rid) { + this.rid = rid; + } + + public void toString(Map params, StringBuilder builder) { + + if (alias != null) { + builder.append(alias); + if (modifier != null) { + modifier.toString(params, builder); + } + } else if (recordAttr != null) { + builder.append(recordAttr); + } else if (rid != null) { + rid.toString(params, builder); + } + if (type != null) { + builder.append(" " + type); + } + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrientGrammar.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrientGrammar.java new file mode 100644 index 00000000000..6d0509ee994 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOrientGrammar.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OOrientGrammar.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OOrientGrammar extends SimpleNode { + public OOrientGrammar(int id) { + super(id); + } + + public OOrientGrammar(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=c7b34343e8c6227da7ed54c169b807cc (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItem.java new file mode 100644 index 00000000000..a127c699de4 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItem.java @@ -0,0 +1,42 @@ +/* Generated By:JJTree: Do not edit this line. OOutPathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OOutPathItem extends OMatchPathItem { + public OOutPathItem(int id) { + super(id); + } + + public OOutPathItem(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("-"); + boolean first = true; + if (this.method.params != null) { + for (OExpression exp : this.method.params) { + if (!first) { + builder.append(", "); + } + builder.append(exp.execute(null, null)); + first = false; + } + } + builder.append("->"); + if (filter != null) { + filter.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=b9cd4c40325a129d9166b281866b7a34 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItemOpt.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItemOpt.java new file mode 100644 index 00000000000..360465e0d24 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OOutPathItemOpt.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. OOutPathItemOpt.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OOutPathItemOpt extends OOutPathItem { + public OOutPathItemOpt(int id) { + super(id); + } + + public OOutPathItemOpt(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=03ffaa23b3d039235588ad2fb032c273 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisBlock.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisBlock.java new file mode 100644 index 00000000000..8d01f10f0b9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisBlock.java @@ -0,0 +1,63 @@ +/* Generated By:JJTree: Do not edit this line. OParenthesisBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.List; +import java.util.Map; + +public class OParenthesisBlock extends OBooleanExpression { + + OBooleanExpression subElement; + + public OParenthesisBlock(int id) { + super(id); + } + + public OParenthesisBlock(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean evaluate(OIdentifiable currentRecord, OCommandContext ctx) { + return subElement.evaluate(currentRecord, ctx); + } + + + public void toString(Map params, StringBuilder builder) { + builder.append("("); + subElement.toString(params, builder); + builder.append(" )"); + } + + @Override + public boolean supportsBasicCalculation() { + return subElement.supportsBasicCalculation(); + } + + @Override + protected int getNumberOfExternalCalculations() { + return subElement.getNumberOfExternalCalculations(); + } + + @Override + protected List getExternalCalculationConditions() { + return subElement.getExternalCalculationConditions(); + } + + @Override public List flatten() { + return subElement.flatten(); + } + + @Override public List getMatchPatternInvolvedAliases() { + return subElement.getMatchPatternInvolvedAliases(); + } +} +/* JavaCC - OriginalChecksum=9a16b6cf7d051382acb94c45067631a9 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisExpression.java new file mode 100644 index 00000000000..299ca2129ac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OParenthesisExpression.java @@ -0,0 +1,67 @@ +/* Generated By:JJTree: Do not edit this line. OParenthesisExpression.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.List; +import java.util.Map; + +public class OParenthesisExpression extends OMathExpression { + + protected OExpression expression; + protected OStatement statement; + + public OParenthesisExpression(int id) { + super(id); + } + + public OParenthesisExpression(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (expression != null) { + return expression.execute(iCurrentRecord, ctx); + } + if (statement != null) { + throw new UnsupportedOperationException("Execution of select in parentheses is not supported"); + } + return super.execute(iCurrentRecord, ctx); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("("); + if (expression != null) { + expression.toString(params, builder); + } else if (statement != null) { + statement.toString(params, builder); + } + builder.append(")"); + } + + @Override protected boolean supportsBasicCalculation() { + if (expression != null) { + return expression.supportsBasicCalculation(); + } + return true; + } + + @Override public boolean isEarlyCalculated() { + // TODO implement query execution and early calculation; + return expression != null && expression.isEarlyCalculated(); + } + + public List getMatchPatternInvolvedAliases() { + return expression.getMatchPatternInvolvedAliases();//TODO also check the statement...? + } +} +/* JavaCC - OriginalChecksum=4656e5faf4f54dc3fc45a06d8e375c35 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPermission.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPermission.java new file mode 100644 index 00000000000..dd7ee3a0697 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPermission.java @@ -0,0 +1,22 @@ +/* Generated By:JJTree: Do not edit this line. OPermission.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OPermission extends SimpleNode { + protected String permission; + + public OPermission(int id) { + super(id); + } + + public OPermission(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append(permission); + } +} +/* JavaCC - OriginalChecksum=576b31633bf93fdbc597f7448fc3c3b3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPositionalParameter.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPositionalParameter.java new file mode 100644 index 00000000000..f96984eb66a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OPositionalParameter.java @@ -0,0 +1,54 @@ +/* Generated By:JJTree: Do not edit this line. OPositionalParameter.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OPositionalParameter extends OInputParameter { + + protected int paramNumber; + + public OPositionalParameter(int id) { + super(id); + } + + public OPositionalParameter(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public String toString() { + return "?"; + } + + public void toString(Map params, StringBuilder builder) { + Object finalValue = bindFromInputParams(params); + if (finalValue == this) { + builder.append("?"); + } else if (finalValue instanceof String) { + builder.append("\""); + builder.append(OExpression.encode(finalValue.toString())); + builder.append("\""); + } else if (finalValue instanceof SimpleNode) { + ((SimpleNode) finalValue).toString(params, builder); + } else { + builder.append(finalValue); + } + } + + public Object bindFromInputParams(Map params) { + if (params != null) { + Object value = params.get(paramNumber); + Object result = toParsedTree(value); + return result; + } + return this; + } + +} +/* JavaCC - OriginalChecksum=f73bea7d9b3994a9d4e79d2c330d8ba2 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProfileStorageStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProfileStorageStatement.java new file mode 100644 index 00000000000..e97a054a94a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProfileStorageStatement.java @@ -0,0 +1,78 @@ +/* Generated By:JJTree: Do not edit this line. OProfileStorageStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import com.orientechnologies.orient.core.storage.OStorage; +import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; +import com.orientechnologies.orient.core.storage.impl.local.statistic.OSessionStoragePerformanceStatistic; + +import java.util.Map; + +public class OProfileStorageStatement extends OStatement { + + protected boolean on; + + public static final String KEYWORD_PROFILE = "PROFILE"; + + public OProfileStorageStatement(int id) { + super(id); + } + + public OProfileStorageStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public Object execute(OSQLAsynchQuery request, OCommandContext context, OProgressListener progressListener) { + try { + ODatabaseDocumentInternal db = getDatabase(); + + final OStorage storage = db.getStorage(); + if (on) { + // activate the profiler + ((OAbstractPaginatedStorage) storage).startGatheringPerformanceStatisticForCurrentThread(); + ODocument result = new ODocument(); + result.field("result", "OK"); + request.getResultListener().result(result); + } else { + // stop the profiler and return the stats + final OSessionStoragePerformanceStatistic performanceStatistic = ((OAbstractPaginatedStorage) storage) + .completeGatheringPerformanceStatisticForCurrentThread(); + + if (performanceStatistic != null) + request.getResultListener().result(performanceStatistic.toDocument()); + else { + ODocument result = new ODocument(); + result.field("result", "Error: profiling of storage was not started."); + request.getResultListener().result(result); + } + + } + return getResult(request); + } finally { + if (request.getResultListener() != null) { + request.getResultListener().end(); + } + } + } + + protected Object getResult(OSQLAsynchQuery request) { + if (request instanceof OSQLSynchQuery) + return ((OSQLSynchQuery) request).getResult(); + + return null; + } + + public void toString(Map params, StringBuilder builder) { + builder.append("PROFILE STORAGE "); + builder.append(on ? "ON" : "OFF"); + } +} + +/* JavaCC - OriginalChecksum=645887712797ae14a17820bfa944f78e (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjection.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjection.java new file mode 100644 index 00000000000..e9f010cb716 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjection.java @@ -0,0 +1,68 @@ +/* Generated By:JJTree: Do not edit this line. OProjection.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OProjection extends SimpleNode { + + List items; + + public OProjection(int id) { + super(id); + } + + public OProjection(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + @Override + public void toString(Map params, StringBuilder builder) { + if (items == null) { + return; + } + boolean first = true; + + // print * before + for (OProjectionItem item : items) { + if (item.isAll()) { + if (!first) { + builder.append(", "); + } + + item.toString(params, builder); + first = false; + } + } + + // and then the rest of the projections + for (OProjectionItem item : items) { + if (!item.isAll()) { + if (!first) { + builder.append(", "); + } + + item.toString(params, builder); + first = false; + } + } + } + + + +} +/* JavaCC - OriginalChecksum=3a650307b53bae626dc063c4b35e62c3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjectionItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjectionItem.java new file mode 100644 index 00000000000..9cefd829088 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OProjectionItem.java @@ -0,0 +1,83 @@ +/* Generated By:JJTree: Do not edit this line. OProjectionItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OProjectionItem extends SimpleNode { + + protected boolean all = false; + + protected OIdentifier alias; + + protected OExpression expression; + + public OProjectionItem(int id) { + super(id); + } + + public OProjectionItem(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean isAll() { + if (all) { + return true; + } + if (expression != null && "*".equals(expression.toString())) { + return true; + } + return false; + } + + public void setAll(boolean all) { + this.all = all; + } + + public OIdentifier getAlias() { + return alias; + } + + public void setAlias(OIdentifier alias) { + this.alias = alias; + } + + public OExpression getExpression() { + return expression; + } + + public void setExpression(OExpression expression) { + this.expression = expression; + } + + public void toString(Map params, StringBuilder builder) { + if (all) { + builder.append("*"); + } else { + expression.toString(params, builder); + if (alias != null) { + + builder.append(" AS "); + alias.toString(params, builder); + } + } + } + + public OIdentifier getDefaultAlias() { + if (expression == null) { + OIdentifier result = new OIdentifier(-1); + result.setValue("null"); + return result; + } + return expression.getDefaultAlias(); + } + +} +/* JavaCC - OriginalChecksum=6d6010734c7434a6f516e2eac308e9ce (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryCursor.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryCursor.java new file mode 100644 index 00000000000..95396ea05ed --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryCursor.java @@ -0,0 +1,88 @@ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Created by luigidellaquila on 02/10/15. + */ +public class OQueryCursor implements Iterator { + private int limit; + private int skip; + private OWhereClause filter; + private Iterator iterator; + private OOrderBy orderBy; + private OCommandContext ctx; + + private OIdentifiable next = null; + private long countFetched = 0; + + public OQueryCursor() { + + } + + public OQueryCursor(Iterator oIdentifiableIterator, OWhereClause filter, OOrderBy orderBy, int skip, int limit, + OCommandContext ctx) { + this.iterator = oIdentifiableIterator; + this.filter = filter; + this.skip = skip; + this.limit = limit; + this.orderBy = orderBy; + this.ctx = ctx; + loadNext(); + } + + private void loadNext() { + if (iterator == null) { + next = null; + return; + } + if (limit > 0 && countFetched >= limit) { + next = null; + return; + } + if (countFetched == 0 && skip > 0) { + for (int i = 0; i < skip; i++) { + next = getNextFromIterator(); + if (next == null) { + return; + } + } + } + next = getNextFromIterator(); + countFetched++; + } + + private OIdentifiable getNextFromIterator() { + while (true) { + if (iterator == null || !iterator.hasNext()) { + return null; + } + + OIdentifiable result = iterator.next(); + if (filter==null || filter.matchesFilters(result, ctx)) { + return result; + } + } + } + + public boolean hasNext() { + return next != null; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + public OIdentifiable next() { + OIdentifiable result = next; + if (result == null) { + throw new NoSuchElementException(); + } + loadNext(); + return result; + } +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryStatement.java new file mode 100644 index 00000000000..c7e062e030b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OQueryStatement.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OQueryStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OQueryStatement extends SimpleNode { + public OQueryStatement(int id) { + super(id); + } + + public OQueryStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=f78d23e607a64459efb18502e47359c1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORebuildIndexStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORebuildIndexStatement.java new file mode 100644 index 00000000000..b68e8c7391a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORebuildIndexStatement.java @@ -0,0 +1,29 @@ +/* Generated By:JJTree: Do not edit this line. ORebuildIndexStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ORebuildIndexStatement extends OStatement { + + protected boolean all = false; + protected OIndexName name; + + public ORebuildIndexStatement(int id) { + super(id); + } + + public ORebuildIndexStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("REBUILD INDEX "); + if (all) { + builder.append("*"); + } else { + name.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=baca3c54112f1c08700ebdb691fa85bd (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORecordAttribute.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORecordAttribute.java new file mode 100644 index 00000000000..f356a835819 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORecordAttribute.java @@ -0,0 +1,28 @@ +/* Generated By:JJTree: Do not edit this line. ORecordAttribute.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ORecordAttribute extends SimpleNode { + + protected String name; + + public ORecordAttribute(int id) { + super(id); + } + + public ORecordAttribute(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append(name); + } +} +/* JavaCC - OriginalChecksum=45ce3cd16399dec7d7ef89f8920d02ae (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OResourcePathItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OResourcePathItem.java new file mode 100644 index 00000000000..826369ab6a1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OResourcePathItem.java @@ -0,0 +1,31 @@ +/* Generated By:JJTree: Do not edit this line. OResourcePathItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OResourcePathItem extends SimpleNode { + protected boolean star = false; + protected OIdentifier identifier; + protected String name; + + public OResourcePathItem(int id) { + super(id); + } + + public OResourcePathItem(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + if (star) { + builder.append("*"); + } else if (identifier != null) { + identifier.toString(params, builder); + } else { + builder.append(name); + } + + } +} +/* JavaCC - OriginalChecksum=b90ccdd61b6adcd40cde2adee353e89f (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORetry.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORetry.java new file mode 100644 index 00000000000..902cc50ca81 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORetry.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. ORetry.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class ORetry extends SimpleNode { + public ORetry(int id) { + super(id); + } + + public ORetry(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=2a27e5f409442f80d26204c901e017c1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OReturnStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OReturnStatement.java new file mode 100644 index 00000000000..1965a0f161d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OReturnStatement.java @@ -0,0 +1,26 @@ +/* Generated By:JJTree: Do not edit this line. OReturnStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OReturnStatement extends OStatement { + protected OExpression expression; + + public OReturnStatement(int id) { + super(id); + } + + public OReturnStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("RETURN"); + if (expression != null) { + builder.append(" "); + expression.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=c72ec860d1fa92cbf52e42ae1c2935c0 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORevokeStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORevokeStatement.java new file mode 100644 index 00000000000..0929980200f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORevokeStatement.java @@ -0,0 +1,43 @@ +/* Generated By:JJTree: Do not edit this line. ORevokeStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public +class ORevokeStatement extends OStatement { + + protected OPermission permission; + protected List resourceChain = new ArrayList(); + protected OIdentifier actor; + + + public ORevokeStatement(int id) { + super(id); + } + + public ORevokeStatement(OrientSql p, int id) { + super(p, id); + } + + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("REVOKE "); + permission.toString(params, builder); + builder.append(" ON "); + boolean first = true; + for (OResourcePathItem res : resourceChain) { + if (!first) { + builder.append("."); + } + res.toString(params, builder); + first = false; + } + builder.append(" FROM "); + actor.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=d483850d10e1562c1b942fcc249278eb (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORid.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORid.java new file mode 100644 index 00000000000..72e26fbf3e0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORid.java @@ -0,0 +1,33 @@ +/* Generated By:JJTree: Do not edit this line. ORid.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class ORid extends SimpleNode { + protected OInteger cluster; + protected OInteger position; + + public ORid(int id) { + super(id); + } + + public ORid(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public String toString(String prefix) { + return "#" + cluster.getValue() + ":" + position.getValue(); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("#" + cluster.getValue() + ":" + position.getValue()); + } +} +/* JavaCC - OriginalChecksum=c2c6d67d7722e29212e438574698d7cd (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORollbackStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORollbackStatement.java new file mode 100644 index 00000000000..995b6f2e74a --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/ORollbackStatement.java @@ -0,0 +1,27 @@ +/* Generated By:JJTree: Do not edit this line. ORollbackStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class ORollbackStatement extends OStatement { + public ORollbackStatement(int id) { + super(id); + } + + public ORollbackStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("ROLLBACK"); + } +} +/* JavaCC - OriginalChecksum=7efe0306e0cec51e035d64cad02ebc30 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OScAndOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OScAndOperator.java new file mode 100644 index 00000000000..f54a548bc8b --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OScAndOperator.java @@ -0,0 +1,45 @@ +/* Generated By:JJTree: Do not edit this line. OScAndOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.sql.operator.OQueryOperator; + +public +class OScAndOperator extends SimpleNode implements OBinaryCompareOperator { + + OQueryOperator lowLevelOperator = null; + public OScAndOperator(int id) { + super(id); + } + + public OScAndOperator(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object iLeft, Object iRight) { + if(lowLevelOperator==null) { + //TODO implement this! + } + if(lowLevelOperator==null) { + throw new UnsupportedOperationException(); + } + return false; + } + + @Override + public String toString() { + return "&&"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } +} +/* JavaCC - OriginalChecksum=12592a24f576571470ce760aff503b30 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectStatement.java new file mode 100644 index 00000000000..145ada9fad1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectStatement.java @@ -0,0 +1,314 @@ +/* Generated By:JJTree: Do not edit this line. OSelectStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.exception.OCommandExecutionException; +import com.orientechnologies.orient.core.id.ORID; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClass; +import com.orientechnologies.orient.core.iterator.ORecordIteratorClassDescendentOrder; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.security.ORole; +import com.orientechnologies.orient.core.metadata.security.ORule; +import com.orientechnologies.orient.core.record.ORecord; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +public class OSelectStatement extends OStatement { + + protected OFromClause target; + + protected OProjection projection; + + protected OWhereClause whereClause; + + protected OGroupBy groupBy; + + protected OOrderBy orderBy; + + protected OUnwind unwind; + + protected OSkip skip; + + protected OLimit limit; + + protected OStorage.LOCKING_STRATEGY lockRecord = null; + + protected OFetchPlan fetchPlan; + + protected OLetClause letClause; + + protected OTimeout timeout; + + protected Boolean parallel; + + protected Boolean noCache; + + public OSelectStatement(int id) { + super(id); + } + + public OSelectStatement(OrientSql p, int id) { + super(p, id); + } + + private OIdentifier getAlias(OProjectionItem item) { + if (item.getAlias() != null) { + return item.getAlias(); + } else { + return item.getDefaultAlias(); + } + + } + + public OProjection getProjection() { + return projection; + } + + public void setProjection(OProjection projection) { + this.projection = projection; + } + + public OFromClause getTarget() { + return target; + } + + public void setTarget(OFromClause target) { + this.target = target; + } + + public OWhereClause getWhereClause() { + return whereClause; + } + + public void setWhereClause(OWhereClause whereClause) { + this.whereClause = whereClause; + } + + public OGroupBy getGroupBy() { + return groupBy; + } + + public void setGroupBy(OGroupBy groupBy) { + this.groupBy = groupBy; + } + + public OOrderBy getOrderBy() { + return orderBy; + } + + public void setOrderBy(OOrderBy orderBy) { + this.orderBy = orderBy; + } + + public OSkip getSkip() { + return skip; + } + + public void setSkip(OSkip skip) { + this.skip = skip; + } + + public OLimit getLimit() { + return limit; + } + + public void setLimit(OLimit limit) { + this.limit = limit; + } + + public OStorage.LOCKING_STRATEGY getLockRecord() { + return lockRecord; + } + + public void setLockRecord(OStorage.LOCKING_STRATEGY lockRecord) { + this.lockRecord = lockRecord; + } + + public OFetchPlan getFetchPlan() { + return fetchPlan; + } + + public void setFetchPlan(OFetchPlan fetchPlan) { + this.fetchPlan = fetchPlan; + } + + public OLetClause getLetClause() { + return letClause; + } + + public void setLetClause(OLetClause letClause) { + this.letClause = letClause; + } + + public void toString(Map params, StringBuilder builder) { + + builder.append("SELECT"); + if (projection != null) { + builder.append(" "); + projection.toString(params, builder); + } + if (target != null) { + builder.append(" FROM "); + target.toString(params, builder); + } + + if (letClause != null) { + builder.append(" "); + letClause.toString(params, builder); + } + + if (whereClause != null) { + builder.append(" WHERE "); + whereClause.toString(params, builder); + } + + if (groupBy != null) { + builder.append(" "); + groupBy.toString(params, builder); + } + + if (orderBy != null) { + builder.append(" "); + orderBy.toString(params, builder); + } + + if (unwind != null) { + builder.append(" "); + unwind.toString(params, builder); + } + + if (skip != null) { + skip.toString(params, builder); + } + + if (limit != null) { + limit.toString(params, builder); + } + + if (lockRecord!=null) { + builder.append(" LOCK "); + switch (lockRecord){ + case DEFAULT: + builder.append("DEFAULT"); + break; + case EXCLUSIVE_LOCK: + builder.append("RECORD"); + break; + case SHARED_LOCK: + builder.append("SHARED"); + break; + case NONE: + builder.append("NONE"); + break; + } + } + + if (fetchPlan != null) { + builder.append(" "); + fetchPlan.toString(params, builder); + } + + if (timeout != null) { + timeout.toString(params, builder); + } + + if (Boolean.TRUE.equals(parallel)) { + builder.append(" PARALLEL"); + } + + if (Boolean.TRUE.equals(noCache)) { + builder.append(" NOCACHE"); + } + } + + public void validate() throws OCommandSQLParsingException { + + } + + private boolean isClassTarget(OFromClause target) { + + return target != null && target.item != null && target.item.identifier != null && target.item.identifier.suffix != null + && target.item.identifier.suffix.identifier != null; + } + + private boolean isIndexTarget(OFromClause target) { + return target != null && target.item != null && target.item.index != null; + } + + public OQueryCursor execute(OCommandContext ctx) { + // TODO projections + return new OQueryCursor(fetchFromTarget(ctx), whereClause, orderBy, calculateSkip(ctx), calculateLimit(ctx), ctx); + } + + private int calculateLimit(OCommandContext ctx) { + return -1;// TODO + } + + private int calculateSkip(OCommandContext ctx) { + return -1;// TODO + } + + private Iterator fetchFromTarget(OCommandContext ctx) { + OFromItem targetItem = target.getItem(); + Iterator result = null; + if (targetItem.cluster != null) { + // TODO + } else if (targetItem.identifier != null) { + if (targetItem.identifier.isBaseIdentifier()) { + String className = targetItem.identifier.toString(); + OClass oClass = getDatabase().getMetadata().getSchema().getClass(className); + if (oClass == null) { + throw new OCommandExecutionException("Class not found in database schema: " + className); + } + if (whereClause != null) { + Iterable resultIterable = whereClause.fetchFromIndexes(oClass, ctx); + if (resultIterable != null) { + result = resultIterable.iterator(); + } + } + if (result == null) { + boolean ascendingOrder = true;// TODO + result = (Iterator) searchInClasses(oClass, true, ascendingOrder); + } + } else { + Object calculationResult = targetItem.identifier.execute(null, ctx); + if (calculationResult instanceof Iterable) { + result = ((Iterable) calculationResult).iterator(); + } else if (calculationResult instanceof OIdentifiable) { + result = (Iterator) Collections.singleton(calculationResult).iterator(); + } else { + // TODO + } + } + } else { + // TODO + } + + return result; + } + + protected Iterator searchInClasses(final OClass iCls, final boolean iPolymorphic, + final boolean iAscendentOrder) { + + final ODatabaseDocumentInternal database = getDatabase(); + database.checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_READ, iCls.getName().toLowerCase(Locale.ENGLISH)); + + final ORID[] range = new ORID[2];// TODO + boolean useCache = false;// TODO + if (iAscendentOrder) + return new ORecordIteratorClass(database, database, iCls.getName(), iPolymorphic, useCache, false).setRange(range[0], + range[1]); + else + return new ORecordIteratorClassDescendentOrder(database, database, iCls.getName(), iPolymorphic).setRange(range[0], + range[1]); + } +} +/* JavaCC - OriginalChecksum=b26959b9726a8cf35d6283eca931da6b (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectWithoutTargetStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectWithoutTargetStatement.java new file mode 100644 index 00000000000..2fcd1801ec0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSelectWithoutTargetStatement.java @@ -0,0 +1,15 @@ +/* Generated By:JJTree: Do not edit this line. OSelectWithoutTargetStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OSelectWithoutTargetStatement extends OSelectStatement { + public OSelectWithoutTargetStatement(int id) { + super(id); + } + + public OSelectWithoutTargetStatement(OrientSql p, int id) { + super(p, id); + } + +} +/* JavaCC - OriginalChecksum=2b0c73e32d84e559188b75251a4d262c (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSimpleBooleanExpression.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSimpleBooleanExpression.java new file mode 100644 index 00000000000..fd1fbba3496 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSimpleBooleanExpression.java @@ -0,0 +1,17 @@ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; + +/** + * Created by luigidellaquila on 21/11/16. + */ +public interface OSimpleBooleanExpression { + + /** + * if the condition involved the current pattern (MATCH statement, eg. $matched.something = foo), + * returns the name of involved pattern aliases ("something" in this case) + * + * @return a list of pattern aliases involved in this condition. Null it does not involve the pattern + */ + List getMatchPatternInvolvedAliases(); +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSkip.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSkip.java new file mode 100644 index 00000000000..3a0a46fd809 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSkip.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OSkip.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OSkip extends SimpleNode { + + protected OInteger num; + + protected OInputParameter inputParam; + + public OSkip(int id) { + super(id); + } + + public OSkip(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + + public void toString(Map params, StringBuilder builder) { + if (num == null && inputParam == null) { + return; + } + builder.append(" SKIP "); + if (num != null) { + num.toString(params, builder); + } else { + inputParam.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=8e13ca184705a8fc1b5939ecefe56a60 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSleepStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSleepStatement.java new file mode 100644 index 00000000000..ae367430813 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSleepStatement.java @@ -0,0 +1,25 @@ +/* Generated By:JJTree: Do not edit this line. OSleepStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OSleepStatement extends OStatement { + + protected OInteger millis; + + public OSleepStatement(int id) { + super(id); + } + + public OSleepStatement(OrientSql p, int id) { + super(p, id); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("SLEEP "); + millis.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=2ea765ee266d4215414908b0e09c0779 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatement.java new file mode 100644 index 00000000000..09ce5ef6bb3 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatement.java @@ -0,0 +1,49 @@ +/* Generated By:JJTree: Do not edit this line. OStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.listener.OProgressListener; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery; + +import java.util.Map; + +public class OStatement extends SimpleNode { + + public static final String CUSTOM_STRICT_SQL = "strictSql"; + + public OStatement(int id) { + super(id); + } + + public OStatement(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + throw new UnsupportedOperationException("missing implementation in " + getClass().getSimpleName()); + } + + public void validate() throws OCommandSQLParsingException { + + } + + @Override + public String toString(String prefix) { + StringBuilder builder = new StringBuilder(); + toString(null, builder); + return builder.toString(); + } + + public Object execute(OSQLAsynchQuery request, OCommandContext context, OProgressListener progressListener) { + throw new UnsupportedOperationException("Unsupported command: " + getClass().getSimpleName()); + } +} +/* JavaCC - OriginalChecksum=589c4dcc8287f430e46d8eb12b0412c5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementCache.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementCache.java new file mode 100644 index 00000000000..3a66f1d09e2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementCache.java @@ -0,0 +1,112 @@ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.config.OGlobalConfiguration; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +/** + * This class is an LRU cache for already parsed SQL statement executors. It stores itself in the storage as a resource. It also + * acts an an entry point for the SQL parser. + * + * @author Luigi Dell'Aquila + */ +public class OStatementCache { + + Map map; + int mapSize; + + /** + * @param size the size of the cache + */ + public OStatementCache(int size) { + this.mapSize = size; + map = new LinkedHashMap(size) { + protected boolean removeEldestEntry(final Map.Entry eldest) { + return super.size() > mapSize; + } + }; + } + + /** + * @param statement an SQL statement + * @return true if the corresponding executor is present in the cache + */ + public boolean contains(String statement) { + synchronized (map) { + return map.containsKey(statement); + } + } + + /** + * returns an already parsed SQL executor, taking it from the cache if it exists or creating a new one (parsing and then putting + * it into the cache) if it doesn't + * + * @param statement the SQL statement + * @param db the current DB instance. If null, cache is ignored and a new executor is created through statement parsing + * @return a statement executor from the cache + */ + public static OStatement get(String statement, ODatabaseDocumentInternal db) { + if (db == null) { + return parse(statement); + } + + OStatementCache resource = db.getStorage().getResource(OStatementCache.class.getSimpleName(), new Callable() { + @Override public OStatementCache call() throws Exception { + return new OStatementCache(OGlobalConfiguration.STATEMENT_CACHE_SIZE.getValueAsInteger()); + } + }); + return resource.get(statement); + } + + /** + * @param statement an SQL statement + * @return the corresponding executor, taking it from the internal cache, if it exists + */ + public OStatement get(String statement) { + OStatement result; + synchronized (map) { + //LRU + result = map.remove(statement); + if (result != null) { + map.put(statement, result); + } + } + if (result == null) { + result = parse(statement); + synchronized (map) { + map.put(statement, result); + } + } + return result; + } + + /** + * parses an SQL statement and returns the corresponding executor + * + * @param statement the SQL statement + * @return the corresponding executor + * @throws OCommandSQLParsingException if the input parameter is not a valid SQL statement + */ + protected static OStatement parse(String statement) throws OCommandSQLParsingException { + try { + final InputStream is = new ByteArrayInputStream(statement.getBytes()); + final OrientSql osql = new OrientSql(is); + OStatement result = osql.parse(); + return result; + } catch (ParseException e) { + throwParsingException(e, statement); + } + return null; + } + + protected static void throwParsingException(ParseException e, String statement) { + throw new OCommandSQLParsingException(e, statement); + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementInternal.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementInternal.java new file mode 100644 index 00000000000..cd1083b9c42 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementInternal.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OStatementInternal.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OStatementInternal extends SimpleNode { + public OStatementInternal(int id) { + super(id); + } + + public OStatementInternal(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=441892d4d3a90ef763379175fb756b22 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementSemicolon.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementSemicolon.java new file mode 100644 index 00000000000..92a165d5107 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OStatementSemicolon.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OStatementSemicolon.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OStatementSemicolon extends SimpleNode { + public OStatementSemicolon(int id) { + super(id); + } + + public OStatementSemicolon(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=dd666171278492fc7540b6aed7c08733 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSuffixIdentifier.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSuffixIdentifier.java new file mode 100644 index 00000000000..5a0075109a2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OSuffixIdentifier.java @@ -0,0 +1,118 @@ +/* Generated By:JJTree: Do not edit this line. OSuffixIdentifier.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiValue; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.record.impl.ODocument; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class OSuffixIdentifier extends SimpleNode { + + protected OIdentifier identifier; + protected ORecordAttribute recordAttribute; + protected boolean star = false; + + public OSuffixIdentifier(int id) { + super(id); + } + + public OSuffixIdentifier(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + if (identifier != null) { + identifier.toString(params, builder); + } else if (recordAttribute != null) { + recordAttribute.toString(params, builder); + } else if (star) { + builder.append("*"); + } + } + + public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) { + if (star) { + return iCurrentRecord; + } + if (identifier != null) { + String varName = identifier.getStringValue(); + if (ctx!=null && ctx.getVariable(varName) != null) { + return ctx.getVariable(varName); + } + if(iCurrentRecord != null) { + return ((ODocument) iCurrentRecord.getRecord()).field(varName); + } + return null; + } + if (recordAttribute != null) { + return ((ODocument) iCurrentRecord.getRecord()).field(recordAttribute.name); + } + return null; + } + + public Object execute(Object currentValue, OCommandContext ctx) { + if (currentValue == null) { + return null; + } + if (star) { + return currentValue; + } + if (identifier != null) { + String varName = identifier.getStringValue(); + if (ctx.getVariable(varName) != null) { + return ctx.getVariable(varName); + } + if (currentValue instanceof OIdentifiable) { + return ((ODocument) ((OIdentifiable) currentValue).getRecord()).field(varName); + } + if (currentValue instanceof Map) { + return ((Map) currentValue).get(varName); + } + if(OMultiValue.isMultiValue(currentValue)){ + Iterator iterator = OMultiValue.getMultiValueIterator(currentValue); + List result = new ArrayList(); + while(iterator.hasNext()){ + result.add(execute(iterator.next(), ctx)); + } + return result; + } + throw new UnsupportedOperationException("Implement SuffixIdentifier!"); + // TODO other cases? + } + if (recordAttribute != null) { + if (currentValue instanceof OIdentifiable) { + return ((ODocument) ((OIdentifiable) currentValue).getRecord()).field(recordAttribute.name); + } + if (currentValue instanceof Map) { + return ((Map) currentValue).get(recordAttribute.name); + } + if(OMultiValue.isMultiValue(currentValue)){ + Iterator iterator = OMultiValue.getMultiValueIterator(currentValue); + List result = new ArrayList(); + while(iterator.hasNext()){ + result.add(execute(iterator.next(), ctx)); + } + return result; + } + throw new UnsupportedOperationException("Implement SuffixIdentifier!"); + // TODO other cases? + } + return null; + } + + public boolean isBaseIdentifier() { + return identifier != null; + } +} +/* JavaCC - OriginalChecksum=5d9be0188c7d6e2b67d691fb88a518f8 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTimeout.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTimeout.java new file mode 100644 index 00000000000..6daf93d5e06 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTimeout.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OTimeout.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OTimeout extends SimpleNode { + public static final String RETURN = "RETURN"; + public static final String EXCEPTION = "EXCEPTION"; + + protected Number val; + protected String failureStrategy; + + public OTimeout(int id) { + super(id); + } + + public OTimeout(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append(" TIMEOUT " + val); + if (failureStrategy != null) { + builder.append(" "); + builder.append(failureStrategy); + } + } +} +/* JavaCC - OriginalChecksum=fef7f5d488f7fca1b6ad0b70c6841931 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseProjectionItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseProjectionItem.java new file mode 100644 index 00000000000..e7c108ef3c2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseProjectionItem.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OTraverseProjectionItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OTraverseProjectionItem extends SimpleNode { + protected boolean star = false; + protected OBaseIdentifier base; + protected OModifier modifier; + + public OTraverseProjectionItem(int id) { + super(id); + } + + public OTraverseProjectionItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + + if (star) { + builder.append("*"); + return; + } + + base.toString(params, builder); + if (modifier != null) { + modifier.toString(params, builder); + } + } + +} +/* JavaCC - OriginalChecksum=0c562254fd4d11266edc0504fd36fc99 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseStatement.java new file mode 100644 index 00000000000..5d21527caa0 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTraverseStatement.java @@ -0,0 +1,81 @@ +/* Generated By:JJTree: Do not edit this line. OTraverseStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OTraverseStatement extends OStatement { + + public enum Strategy { + DEPTH_FIRST, BREADTH_FIRST + } + + protected List projections = new ArrayList(); + + protected OFromClause target; + + protected OWhereClause whereClause; + + protected OLimit limit; + + protected Strategy strategy; + + protected OInteger maxDepth; + + public OTraverseStatement(int id) { + super(id); + } + + public OTraverseStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("TRAVERSE "); + boolean first = true; + for (OTraverseProjectionItem item : projections) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + + if (target != null) { + builder.append(" FROM "); + target.toString(params, builder); + } + + if (maxDepth != null) { + builder.append(" MAXDEPTH "); + maxDepth.toString(params, builder); + } + + if (whereClause != null) { + builder.append(" WHILE "); + whereClause.toString(params, builder); + } + + if (limit != null) { + builder.append(" "); + limit.toString(params, builder); + } + + if (strategy != null) { + builder.append(" strategy "); + switch (strategy) { + case BREADTH_FIRST: + builder.append("breadth_first"); + break; + case DEPTH_FIRST: + builder.append("depth_first"); + break; + } + } + + } + +} +/* JavaCC - OriginalChecksum=47399a3a3d5a423768bbdc70ee957464 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClassStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClassStatement.java new file mode 100644 index 00000000000..c797c880a98 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClassStatement.java @@ -0,0 +1,38 @@ +/* Generated By:JJTree: Do not edit this line. OTruncateClassStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public +class OTruncateClassStatement extends OStatement { + + protected OIdentifier className; + protected boolean polymorphic = false; + protected boolean unsafe = false; + + public OTruncateClassStatement(int id) { + super(id); + } + + public OTruncateClassStatement(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override public void toString(Map params, StringBuilder builder) { + builder.append("TRUNCATE CLASS "+className.toString()); + if(polymorphic){ + builder.append(" POLYMORPHIC"); + } + if(unsafe){ + builder.append(" UNSAFE"); + } + } +} +/* JavaCC - OriginalChecksum=301f993f6ba2893cb30c8f189674b974 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClusterStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClusterStatement.java new file mode 100644 index 00000000000..b43117bc005 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateClusterStatement.java @@ -0,0 +1,40 @@ +/* Generated By:JJTree: Do not edit this line. OTruncateClusterStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OTruncateClusterStatement extends OStatement { + + public OIdentifier clusterName; + public OInteger clusterNumber; + public boolean unsafe = false; + + public OTruncateClusterStatement(int id) { + super(id); + } + + public OTruncateClusterStatement(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("TRUNCATE CLUSTER "); + if (clusterName != null) { + clusterName.toString(params, builder); + } else if (clusterNumber != null) { + clusterNumber.toString(params, builder); + } + if (unsafe) { + builder.append(" UNSAFE"); + } + + } +} +/* JavaCC - OriginalChecksum=301f993f6ba2893cb30c8f189674b974 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateRecordStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateRecordStatement.java new file mode 100644 index 00000000000..d9b9a84912d --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OTruncateRecordStatement.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OTruncateRecordStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.List; +import java.util.Map; + +public class OTruncateRecordStatement extends OStatement { + protected ORid record; + protected List records; + + public OTruncateRecordStatement(int id) { + super(id); + } + + public OTruncateRecordStatement(OrientSql p, int id) { + super(p, id); + } + + @Override + public void toString(Map params, StringBuilder builder) { + builder.append("TRUNCATE RECORD "); + if (record != null) { + record.toString(params, builder); + } else { + builder.append("["); + boolean first = true; + for (ORid r : records) { + if (!first) { + builder.append(","); + } + r.toString(params, builder); + first = false; + } + builder.append("]"); + } + } +} +/* JavaCC - OriginalChecksum=9da68e9fe4c4bf94a12d8a6f8864097a (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUnwind.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUnwind.java new file mode 100644 index 00000000000..90e3a6448e2 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUnwind.java @@ -0,0 +1,36 @@ +/* Generated By:JJTree: Do not edit this line. OGroupBy.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OUnwind extends SimpleNode { + + protected List items = new ArrayList(); + + public OUnwind(int id) { + super(id); + } + + public OUnwind(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + builder.append("UNWIND "); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + builder.append(", "); + } + items.get(i).toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=4739190aa6c1a3533a89b76a15bd6fdf (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateAddItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateAddItem.java new file mode 100644 index 00000000000..d2f6411d28e --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateAddItem.java @@ -0,0 +1,32 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateAddItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OUpdateAddItem extends SimpleNode { + + protected OIdentifier left; + protected OExpression right; + + public OUpdateAddItem(int id) { + super(id); + } + + public OUpdateAddItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" = "); + right.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=769679aa2d2d8df58a13210152b50a9d (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateEdgeStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateEdgeStatement.java new file mode 100644 index 00000000000..2db4fd5f082 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateEdgeStatement.java @@ -0,0 +1,24 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateEdgeStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OUpdateEdgeStatement extends OUpdateStatement { + public OUpdateEdgeStatement(int id) { + super(id); + } + + public OUpdateEdgeStatement(OrientSql p, int id) { + super(p, id); + } + + protected String getStatementType() { + return "UPDATE EDGE "; + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=496f32976ee84e3a3a89d1410dc134c5 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateIncrementItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateIncrementItem.java new file mode 100644 index 00000000000..00fb7b57e77 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateIncrementItem.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateIncrementItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OUpdateIncrementItem extends SimpleNode { + protected OIdentifier left; + protected OModifier leftModifier; + protected OExpression right; + + public OUpdateIncrementItem(int id) { + super(id); + } + + public OUpdateIncrementItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + if (leftModifier != null) { + leftModifier.toString(params, builder); + } + builder.append(" = "); + right.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=94dd82febb904e4e31130bdcbbb48fe3 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateItem.java new file mode 100644 index 00000000000..aa481519d0f --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateItem.java @@ -0,0 +1,59 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OUpdateItem extends SimpleNode { + public static final int OPERATOR_EQ = 0; + public static final int OPERATOR_PLUSASSIGN = 1; + public static final int OPERATOR_MINUSASSIGN = 2; + public static final int OPERATOR_STARASSIGN = 3; + public static final int OPERATOR_SLASHASSIGN = 4; + + protected OIdentifier left; + protected OModifier leftModifier; + protected int operator; + protected OExpression right; + + public OUpdateItem(int id) { + super(id); + } + + public OUpdateItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + if (leftModifier != null) { + leftModifier.toString(params, builder); + } + switch (operator) { + case OPERATOR_EQ: + builder.append(" = "); + break; + case OPERATOR_PLUSASSIGN: + builder.append(" += "); + break; + case OPERATOR_MINUSASSIGN: + builder.append(" -= "); + break; + case OPERATOR_STARASSIGN: + builder.append(" *= "); + break; + case OPERATOR_SLASHASSIGN: + builder.append(" /= "); + break; + + } + right.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=df7444be87bba741316df8df0d653600 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateOperations.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateOperations.java new file mode 100644 index 00000000000..ece82fb94c1 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateOperations.java @@ -0,0 +1,109 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateOperations.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OUpdateOperations extends SimpleNode { + protected static final int TYPE_SET = 0; + protected static final int TYPE_PUT = 1; + protected static final int TYPE_MERGE = 2; + protected static final int TYPE_CONTENT = 3; + protected static final int TYPE_INCREMENT = 4; + protected static final int TYPE_ADD = 5; + protected static final int TYPE_REMOVE = 6; + + protected int type; + + protected List updateItems = new ArrayList(); + + protected List updatePutItems = new ArrayList(); + + protected OJson json; + + protected List updateIncrementItems = new ArrayList(); + + protected List updateRemoveItems = new ArrayList(); + + public OUpdateOperations(int id) { + super(id); + } + + public OUpdateOperations(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + boolean first = true; + switch (type) { + case TYPE_SET: + builder.append("SET "); + for (OUpdateItem item : this.updateItems) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + break; + case TYPE_PUT: + builder.append("PUT "); + for (OUpdatePutItem item : this.updatePutItems) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + break; + case TYPE_MERGE: + builder.append("MERGE "); + json.toString(params, builder); + break; + case TYPE_CONTENT: + builder.append("CONTENT "); + json.toString(params, builder); + break; + case TYPE_INCREMENT: + builder.append("INCREMENT "); + for (OUpdateIncrementItem item : this.updateIncrementItems) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + break; + case TYPE_ADD: + builder.append("ADD "); + for (OUpdateIncrementItem item : this.updateIncrementItems) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + break; + case TYPE_REMOVE: + builder.append("REMOVE "); + for (OUpdateRemoveItem item : this.updateRemoveItems) { + if (!first) { + builder.append(", "); + } + item.toString(params, builder); + first = false; + } + break; + + } + } + +} +/* JavaCC - OriginalChecksum=0eca3b3e4e3d96c42db57b7cd89cf755 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdatePutItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdatePutItem.java new file mode 100644 index 00000000000..d685c1d4909 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdatePutItem.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OUpdatePutItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OUpdatePutItem extends SimpleNode { + + protected OIdentifier left; + protected OExpression key; + protected OExpression value; + + public OUpdatePutItem(int id) { + super(id); + } + + public OUpdatePutItem(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + builder.append(" = "); + key.toString(params, builder); + builder.append(", "); + value.toString(params, builder); + } +} +/* JavaCC - OriginalChecksum=a38339c33ebf0a8b21e76ddb278f4958 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateRemoveItem.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateRemoveItem.java new file mode 100644 index 00000000000..4b941581b44 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateRemoveItem.java @@ -0,0 +1,39 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateRemoveItem.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import java.util.Map; + +public class OUpdateRemoveItem extends SimpleNode { + + OIdentifier left; + OModifier leftModifier; + OExpression right; + + public OUpdateRemoveItem(int id) { + super(id); + } + + public OUpdateRemoveItem(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. + **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void toString(Map params, StringBuilder builder) { + left.toString(params, builder); + if (leftModifier != null) { + leftModifier.toString(params, builder); + } + if (right != null) { + builder.append(" = "); + right.toString(params, builder); + } + } +} +/* JavaCC - OriginalChecksum=72e240d3dc1196fdea69e8fdc2bd69ca (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateStatement.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateStatement.java new file mode 100644 index 00000000000..1bfe43df029 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OUpdateStatement.java @@ -0,0 +1,107 @@ +/* Generated By:JJTree: Do not edit this line. OUpdateStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.orient.core.storage.OStorage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OUpdateStatement extends OStatement { + public OFromClause target; + + protected List operations = new ArrayList(); + + protected boolean upsert = false; + + protected boolean returnBefore = false; + protected boolean returnAfter = false; + protected boolean returnCount = false; + protected OProjection returnProjection; + + public OLetClause let; + public OWhereClause whereClause; + + public OStorage.LOCKING_STRATEGY lockRecord = null; + + public OLimit limit; + public OTimeout timeout; + + public OUpdateStatement(int id) { + super(id); + } + + public OUpdateStatement(OrientSql p, int id) { + super(p, id); + } + + public void toString(Map params, StringBuilder builder) { + builder.append(getStatementType()); + if(target!=null){ + target.toString(params, builder); + } + + for (OUpdateOperations ops : this.operations) { + builder.append(" "); + ops.toString(params, builder); + } + + if (upsert) { + builder.append(" UPSERT"); + } + + if (returnBefore || returnAfter || returnCount) { + builder.append(" RETURN"); + if (returnBefore) { + builder.append(" BEFORE"); + } else if (returnAfter){ + builder.append(" AFTER"); + } else{ + builder.append(" COUNT"); + } + if (returnProjection != null) { + builder.append(" "); + returnProjection.toString(params, builder); + } + } + if(let != null){ + builder.append(" "); + let.toString(params, builder); + } + if (whereClause != null) { + builder.append(" WHERE "); + whereClause.toString(params, builder); + } + + if (lockRecord!=null) { + builder.append(" LOCK "); + switch (lockRecord){ + case DEFAULT: + builder.append("DEFAULT"); + break; + case EXCLUSIVE_LOCK: + builder.append("RECORD"); + break; + case SHARED_LOCK: + builder.append("SHARED"); + break; + case NONE: + builder.append("NONE"); + break; + } + } + if (limit != null) { + limit.toString(params, builder); + } + if (timeout != null) { + timeout.toString(params, builder); + } + } + + protected String getStatementType() { + return "UPDATE "; + } + +} +/* JavaCC - OriginalChecksum=093091d7273f1073ad49f2a2bf709a53 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWait.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWait.java new file mode 100644 index 00000000000..ff46fb13eec --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWait.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OWait.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OWait extends SimpleNode { + public OWait(int id) { + super(id); + } + + public OWait(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=e77b1496216c4d2b2f8ad564da0c3dac (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWhereClause.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWhereClause.java new file mode 100644 index 00000000000..9fe4049caac --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWhereClause.java @@ -0,0 +1,271 @@ +/* Generated By:JJTree: Do not edit this line. OWhereClause.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +import com.orientechnologies.common.collection.OMultiCollectionIterator; +import com.orientechnologies.common.util.OSizeable; +import com.orientechnologies.orient.core.command.OCommandContext; +import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; +import com.orientechnologies.orient.core.db.record.OIdentifiable; +import com.orientechnologies.orient.core.index.*; +import com.orientechnologies.orient.core.metadata.schema.OClass; +import com.orientechnologies.orient.core.metadata.schema.OType; + +import java.util.*; + +public class OWhereClause extends SimpleNode { + protected OBooleanExpression baseExpression; + + protected List flattened; + + public OWhereClause(int id) { + super(id); + } + + public OWhereClause(OrientSql p, int id) { + super(p, id); + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public boolean matchesFilters(OIdentifiable currentRecord, OCommandContext ctx) { + if (baseExpression == null) { + return true; + } + return baseExpression.evaluate(currentRecord, ctx); + } + + public void toString(Map params, StringBuilder builder) { + if (baseExpression == null) { + return; + } + baseExpression.toString(params, builder); + } + + /** + * estimates how many items of this class will be returned applying this filter + * + * @param oClass + * + * @return an estimation of the number of records of this class returned applying this filter, 0 if and only if sure that no + * records are returned + */ + public long estimate(OClass oClass, long threshold, OCommandContext ctx) { + long count = oClass.count(); + if (count > 1) { + count = count / 2; + } + if (count < threshold) { + return count; + } + + long indexesCount = 0l; + List flattenedConditions = flatten(); + Set> indexes = oClass.getIndexes(); + for (OAndBlock condition : flattenedConditions) { + Map conditions = getEqualityOperations(condition, ctx); + long conditionEstimation = Long.MAX_VALUE; + for (OIndex index : indexes) { + List indexedFields = index.getDefinition().getFields(); + int nMatchingKeys = 0; + for (String indexedField : indexedFields) { + if (conditions.containsKey(indexedField)) { + nMatchingKeys++; + } else { + break; + } + } + if (nMatchingKeys > 0) { + long newCount = estimateFromIndex(index, conditions, nMatchingKeys); + if (newCount < conditionEstimation) { + conditionEstimation = newCount; + } + } + } + if (conditionEstimation > count) { + return count; + } + indexesCount += conditionEstimation; + } + return Math.min(indexesCount, count); + } + + private long estimateFromIndex(OIndex index, Map conditions, int nMatchingKeys) { + if (nMatchingKeys < 1) { + throw new IllegalArgumentException("Cannot estimate from an index with zero keys"); + } + OIndexDefinition definition = index.getDefinition(); + List definitionFields = definition.getFields(); + Object key = null; + if (definition instanceof OPropertyIndexDefinition) { + key = convert(conditions.get(definitionFields.get(0)), definition.getTypes()[0]); + } else if (definition instanceof OCompositeIndexDefinition) { + key = new OCompositeKey(); + for (int i = 0; i < nMatchingKeys; i++) { + Object keyValue = convert(conditions.get(definitionFields.get(i)), definition.getTypes()[i]); + ((OCompositeKey) key).addKey(keyValue); + } + } + if (key != null) { + Object result = null; + if (conditions.size() == definitionFields.size()) { + result = index.get(key); + } else if (index.supportsOrderedIterations()) { + result = index.iterateEntriesBetween(key, true, key, true, true); + } + if (result instanceof OIdentifiable) { + return 1; + } + if (result instanceof Collection) { + return ((Collection) result).size(); + } + if (result instanceof OSizeable) { + return ((OSizeable) result).size(); + } + if (result instanceof Iterable) { + result = ((Iterable) result).iterator(); + } + if (result instanceof Iterator) { + int i = 0; + while (((Iterator) result).hasNext()) { + ((Iterator) result).next(); + i++; + } + return i; + } + } + return Long.MAX_VALUE; + } + + public Iterable fetchFromIndexes(OClass oClass, OCommandContext ctx) { + + List flattenedConditions = flatten(); + if (flattenedConditions == null || flattenedConditions.size() == 0) { + return null; + } + Set> indexes = oClass.getIndexes(); + List bestIndexes = new ArrayList(); + List> indexConditions = new ArrayList>(); + for (OAndBlock condition : flattenedConditions) { + Map conditions = getEqualityOperations(condition, ctx); + long conditionEstimation = Long.MAX_VALUE; + OIndex bestIndex = null; + Map bestCondition = null; + + for (OIndex index : indexes) { + List indexedFields = index.getDefinition().getFields(); + int nMatchingKeys = 0; + for (String indexedField : indexedFields) { + if (conditions.containsKey(indexedField)) { + nMatchingKeys++; + } else { + break; + } + } + if (nMatchingKeys > 0) { + long newCount = estimateFromIndex(index, conditions, nMatchingKeys); + if (newCount >= 0 && newCount <= conditionEstimation) { + conditionEstimation = newCount; + bestIndex = index; + bestCondition = conditions; + } + } + } + if (bestIndex == null) { + return null; + } + bestIndexes.add(bestIndex); + indexConditions.add(bestCondition); + } + OMultiCollectionIterator result = new OMultiCollectionIterator(); + + for (int i = 0; i < bestIndexes.size(); i++) { + OIndex index = bestIndexes.get(i); + Map condition = indexConditions.get(i); + result.add(fetchFromIndex(index, indexConditions.get(i))); + } + return result; + } + + private Iterable fetchFromIndex(OIndex index, Map conditions) { + OIndexDefinition definition = index.getDefinition(); + List definitionFields = definition.getFields(); + Object key = null; + if (definition instanceof OPropertyIndexDefinition) { + key = convert(conditions.get(definitionFields.get(0)), definition.getTypes()[0]); + } else if (definition instanceof OCompositeIndexDefinition) { + key = new OCompositeKey(); + for (int i = 0; i < definitionFields.size(); i++) { + String keyName = definitionFields.get(i); + if (!conditions.containsKey(keyName)) { + break; + } + Object keyValue = convert(conditions.get(keyName), definition.getTypes()[i]); + ((OCompositeKey) key).addKey(conditions.get(keyName)); + } + } + if (key != null) { + final Object result = index.get(key); + if (result == null) { + return Collections.EMPTY_LIST; + } + if (result instanceof Iterable) { + return (Iterable) result; + } + if (result instanceof Iterator) { + return new Iterable() { + @Override + public Iterator iterator() { + return (Iterator) result; + } + }; + } + return Collections.singleton(result); + } + return null; + } + + private Object convert(Object o, OType oType) { + return OType.convert(o, oType.getDefaultJavaType()); + } + + private Map getEqualityOperations(OAndBlock condition, OCommandContext ctx) { + Map result = new HashMap(); + for (OBooleanExpression expression : condition.subBlocks) { + if (expression instanceof OBinaryCondition) { + OBinaryCondition b = (OBinaryCondition) expression; + if (b.operator instanceof OEqualsCompareOperator) { + if (b.left.isBaseIdentifier() && b.right.isEarlyCalculated()) { + result.put(b.left.toString(), b.right.execute(null, ctx)); + } + } + } + } + return result; + } + + public List flatten() { + if (this.baseExpression == null) { + return Collections.EMPTY_LIST; + } + if (flattened == null) { + flattened = this.baseExpression.flatten(); + } + // TODO remove false conditions (contraddictions) + return flattened; + + } + + public List getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) { + if (baseExpression == null) { + return null; + } + return this.baseExpression.getIndexedFunctionConditions(iSchemaClass, database); + } +} +/* JavaCC - OriginalChecksum=e8015d01ce1ab2bc337062e9e3f2603e (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWithinOperator.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWithinOperator.java new file mode 100644 index 00000000000..ee125df8ebc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OWithinOperator.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. OWithinOperator.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public class OWithinOperator extends SimpleNode implements OBinaryCompareOperator { + public OWithinOperator(int id) { + super(id); + } + + public OWithinOperator(OrientSql p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public boolean execute(Object left, Object right) { + throw new UnsupportedOperationException(toString() + " operator cannot be evaluated in this context"); + } + + @Override + public String toString() { + return "WITHIN"; + } + + @Override public boolean supportsBasicCalculation() { + return true; + } + + +} +/* JavaCC - OriginalChecksum=e627b2d87bdac6de681d462e4b764288 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Oparse.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Oparse.java new file mode 100644 index 00000000000..40a57bb33f8 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/Oparse.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. Oparse.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class Oparse extends SimpleNode { + public Oparse(int id) { + super(id); + } + + public Oparse(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=9967d5e420c95913a45cded5863b8a91 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OparseScript.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OparseScript.java new file mode 100644 index 00000000000..82ed4d19a71 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OparseScript.java @@ -0,0 +1,21 @@ +/* Generated By:JJTree: Do not edit this line. OparseScript.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package com.orientechnologies.orient.core.sql.parser; + +public +class OparseScript extends SimpleNode { + public OparseScript(int id) { + super(id); + } + + public OparseScript(OrientSql p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(OrientSqlVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=5fdc32eed51a7a14e93e0d983b5e32c1 (do not edit this line) */ diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSQL.jj b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSQL.jj new file mode 100644 index 00000000000..c22ff233b43 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSQL.jj @@ -0,0 +1,5931 @@ +/*@bgen(jjtree) Generated By:JJTree: Do not edit this line. OrientSQL.jj */ +/*@egen*//* + * + * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) + * * + * * Licensed 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. + * * + * * For more information: http://www.orientechnologies.com + * + */ + + +options { + + JDK_VERSION = "1.6"; + + + STATIC=false; + USER_CHAR_STREAM = true ; + JAVA_UNICODE_ESCAPE=true; + +} + +PARSER_BEGIN(OrientSql) + +package com.orientechnologies.orient.core.sql.parser; + +import java.io.InputStream; +import java.util.List; +import java.util.ArrayList; + +/** Orient Database Sql grammar. */ +public class OrientSql/*@bgen(jjtree)*/implements OrientSqlTreeConstants/*@egen*/ {/*@bgen(jjtree)*/ + protected JJTOrientSqlState jjtree = new JJTOrientSqlState(); + +/*@egen*/ + + private int inputParamCount = 0; + + /** Main entry point. For development purpose only */ + public static void main(String args[]) { + System.out.println("Reading from standard input..."); + OrientSql t = new OrientSql(System.in); + try { + OStatement n = t.parse(); + n.dump(""); + System.out.println("Thank you."); + } catch (Exception e) { + System.out.println("Oops."); + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + public OrientSql(InputStream stream) { + this(new JavaCharStream(stream)); + } + +} + +PARSER_END(OrientSql) + +SKIP : +{ + " " +| "\t" +| "\n" +| "\r" +} + + +/* reserved words */ +TOKEN: +{ + < SELECT: ( "s" | "S" ) ( "e" | "E" ) ( "l" | "L" ) ( "e" | "E" ) ( "c" | "C" ) ( "t" | "T" ) > + | + < TRAVERSE: ( "t" | "T") ( "r" | "R") ( "a" | "A") ( "v" | "V") ( "e" | "E") ( "r" | "R") ( "s" | "S") ( "e" | "E") > + | + < INSERT: ( "i" | "I" ) ( "n" | "N" ) ( "s" | "S" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) > + | + < CREATE: ( "c" | "C" ) ( "r" | "R" ) ( "e" | "E" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < DELETE: ( "d" | "D" ) ( "e" | "E" ) ( "l" | "L" ) ( "e" | "E" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < VERTEX: ( "v" | "V" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) ( "e" | "E" ) ( "x" | "X" ) > + | + < EDGE: ( "e" | "E" ) ( "d" | "D" ) ( "g" | "G" ) ( "e" | "E" ) > + | + < UPDATE: ( "u" | "U" ) ( "p" | "P" ) ( "d" | "D" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) > + | + < UPSERT: ( "u" | "U" ) ( "p" | "P" ) ( "s" | "S" ) ( "e" | "E" ) ( "r" | "R" ) ( "t" | "T" ) > + | + < FROM: ( "f" | "F" ) ( "r" | "R" ) ( "o" | "O" ) ( "m" | "M" ) > + | + < TO: ( "t" | "T" ) ( "o" | "O" ) > + | + < WHERE: ( "w" | "W" ) ( "h" | "H" ) ( "e" | "E" ) ( "r" | "R" ) ( "e" | "E" ) > + | + < WHILE: ( "w" | "W" ) ( "h" | "H" ) ( "i" | "I" ) ( "l" | "L" ) ( "e" | "E" ) > + | + < INTO: ( "i" | "I" ) ( "n" | "N" ) ( "t" | "T" ) ( "o" | "O" ) > + | + < VALUES: ( "v" | "V" ) ( "a" | "A" ) ( "l" | "L" ) ( "u" | "U" ) ( "e" | "E" ) ( "s" | "S" )> + | + < SET: ( "s" | "S" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < ADD: ( "a" | "A" ) ( "d" | "D" ) ( "d" | "D" ) > + | + < PUT: ( "p" | "P" ) ( "u" | "U" ) ( "t" | "T" ) > + | + < MERGE: ( "m" | "M" ) ( "e" | "E" ) ( "r" | "R" ) ( "g" | "G" ) ( "e" | "E" ) > + | + < CONTENT: ( "c" | "C" ) ( "o" | "O" ) ( "n" | "N" ) ( "t" | "T" ) ( "e" | "E" ) ( "n" | "N" ) ( "t" | "T" ) > + | + < REMOVE: ( "r" | "R" ) ( "e" | "E" ) ( "m" | "M" ) ( "o" | "O" ) ( "v" | "V" ) ( "e" | "E" ) > + | + < INCREMENT: ( "i" | "I" ) ( "n" | "N" ) ( "c" | "C" ) ( "r" | "R" ) ( "e" | "E" ) ( "m" | "M" ) ( "e" | "E" ) ( "n" | "N" ) ( "t" | "T" ) > + | + < AND: ( "a" | "A" ) ( "n" | "N" ) ( "d" | "D" ) > + | + < OR: ( "o" | "O" ) ( "r" | "R" ) > + | + < NULL: ( "N" | "n" ) ( "U" | "u" ) ( "L" | "l" ) ( "L" | "l" ) > + | + < DEFINED: ( "D" | "d" ) ( "E" | "e" ) ( "F" | "f" ) ( "I" | "i" ) ( "N" | "n" ) ( "E" | "e" ) ( "D" | "d" ) > + | + < ORDER: ( "o" | "O" ) ( "r" | "R" ) ( "d" | "D" ) ( "e" | "E" ) ( "r" | "R" ) > + | + < GROUP: ( "g" | "G" ) ( "r" | "R" ) ( "o" | "O" ) ( "u" | "U" ) ( "p" | "P" ) > + | + < BY: ( "b" | "B" ) ( "y" | "Y" ) > + | + < LIMIT: ( "l" | "L" ) ( "i" | "I" ) ( "m" | "M" ) ( "i" | "I" ) ( "t" | "T" ) > + | + < SKIP2: ( "s" | "S" ) ( "k" | "K" ) ( "i" | "I" ) ( "p" | "P" ) > + | + < OFFSET: ( "o" | "O" ) ( "f" | "F" ) ( "f" | "F" ) ( "s" | "S" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < TIMEOUT: ( "t" | "T" ) ( "i" | "I" ) ( "m" | "M" ) ( "e" | "E" ) ( "o" | "O" ) ( "u" | "U" ) ( "t" | "T" ) > + | + < ASC: ( "a" | "A" ) ( "s" | "S" ) ( "c" | "C" ) > + | + < AS: ( "a" | "A" ) ( "s" | "S" ) > + | + < DESC: ( "d" | "D" ) ( "e" | "E" ) ( "s" | "S" ) ( "c" | "C" ) > + | + < FETCHPLAN: ( "f" | "F" ) ( "e" | "E" ) ( "t" | "T" ) ( "c" | "C" ) ( "h" | "H" ) ( "p" | "P" ) ( "l" | "L" ) ( "a" | "A" ) ( "n" | "N" ) > + | + < RETURN: ( "r" | "R" ) ( "e" | "E" ) ( "t" | "T" ) ( "u" | "U" ) ( "r" | "R" ) ( "n" | "N" ) > + | + < BEFORE: ( "b" | "B" ) ( "e" | "E" ) ( "f" | "F" ) ( "o" | "O" ) ( "r" | "R" ) ( "e" | "E" ) > + | + < AFTER: ( "a" | "A" ) ( "f" | "F" ) ( "t" | "T" ) ( "e" | "E" ) ( "r" | "R" ) > + | + < LOCK: ( "l" | "L" ) ( "o" | "O" ) ( "c" | "C" ) ( "k" | "K" ) > + | + < RECORD: ( "r" | "R" ) ( "e" | "E" ) ( "c" | "C" ) ( "o" | "O" ) ( "r" | "R" ) ( "d" | "D" ) > + | + < WAIT: ( "w" | "W" ) ( "a" | "A" ) ( "i" | "I" ) ( "t" | "T" ) > + | + < RETRY: ( "r" | "R" ) ( "e" | "E" ) ( "t" | "T" ) ( "r" | "R" ) ( "y" | "Y" ) > + | + < LET: ( "l" | "L" ) ( "e" | "E" ) ( "t" | "T" ) > + | + < NOCACHE: ( "n" | "N" ) ( "o" | "O" ) ( "c" | "C" ) ( "a" | "A" ) ( "c" | "C" ) ( "h" | "H" ) ( "e" | "E" ) > + | + < UNSAFE: ( "u" | "U" ) ( "n" | "N" ) ( "s" | "S" ) ( "a" | "A" ) ( "f" | "F" ) ( "e" | "E" ) > + | + < PARALLEL: ( "p" | "P" ) ( "a" | "A" ) ( "r" | "R" ) ( "a" | "A" ) ( "l" | "L" ) ( "l" | "L" ) ( "e" | "E" ) ( "l" | "L" ) > + | + < STRATEGY: ( "s" | "S" ) ( "t" | "T" ) ( "r" | "R" ) ( "a" | "A" ) ( "t" | "T" ) ( "e" | "E" ) ( "g" | "G" ) ( "y" | "Y" ) > + | + < DEPTH_FIRST: ( "d" | "d" ) ( "e" | "E" ) ( "p" | "P" ) ( "t" | "T" ) ( "h" | "H" ) ( "_" ) ( "f" | "F" ) ( "i" | "I" ) ( "r" | "R" ) ( "s" | "S" ) ( "t" | "T" ) > + | + < BREADTH_FIRST: ( "b" | "B" ) ( "r" | "R" ) ( "e" | "E" ) ( "a" | "A" ) ( "d" | "D" ) ( "t" | "T" ) ( "h" | "H" ) ( "_" ) ( "f" | "F" ) ( "i" | "I" ) ( "r" | "R" ) ( "s" | "S" ) ( "t" | "T" ) > + | + < LUCENE: ( "l" | "L" ) ( "u" | "U" ) ( "c" | "C" ) ( "e" | "E" ) ( "n" | "N" ) ( "e" | "E" ) > + | + < THIS: "@this" > + | + < RECORD_ATTRIBUTE: | | | | | > + | + < #RID_ATTR: "@" ( ( "r" | "R" ) ( "i" | "I" ) ( "d" | "D" )) > + | + < #CLASS_ATTR: "@class" > + | + < #VERSION_ATTR: "@version" > + | + < #SIZE_ATTR: "@size" > + | + < #TYPE_ATTR: "@type" > + | + < #RAW_ATTR: "@raw" > +} + + +/* LITERALS */ + +TOKEN : +{ + < INTEGER_LITERAL: + (["l","L"])? + | (["l","L"])? + | (["l","L"])? + > +| + < #DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* > +| + < #HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ > +| + < #OCTAL_LITERAL: "0" (["0"-"7"])* > +| + < FLOATING_POINT_LITERAL: + + | + > +| + < #DECIMAL_FLOATING_POINT_LITERAL: + (["0"-"9"])+ "." (["0"-"9"])* ()? (["f","F","d","D"])? + | "." (["0"-"9"])+ ()? (["f","F","d","D"])? + | (["0"-"9"])+ (["f","F","d","D"])? + | (["0"-"9"])+ ()? ["f","F","d","D"] + > +| + < #DECIMAL_EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > +| + < #HEXADECIMAL_FLOATING_POINT_LITERAL: + "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])+ (".")? (["f","F","d","D"])? + | "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])* "." (["0"-"9","a"-"f","A"-"F"])+ (["f","F","d","D"])? + > +| + < #HEXADECIMAL_EXPONENT: ["p","P"] (["+","-"])? (["0"-"9"])+ > +| + < CHARACTER_LITERAL: + "'" + ( (~["'","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\""] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + ) + "'" + > +| + < STRING_LITERAL: + ( + "\"" + ( (~["\"","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\""] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + )* + "\"" + ) + | + ( + "'" + ( (~["\'","\\","\n","\r"]) + | ("\\" + ( ["n","t","b","r","f","\\","'","\""] + | ["0"-"7"] ( ["0"-"7"] )? + | ["0"-"3"] ["0"-"7"] ["0"-"7"] + ) + ) + )* + "'" + ) + > + | + < INTEGER_RANGE: + ()? ()? + > + | + < TRUE: "true" > + | + < FALSE: "false" > +} + + + +/* SEPARATORS */ + +TOKEN : +{ + < LPAREN: "(" > +| < RPAREN: ")" > +| < LBRACE: "{" > +| < RBRACE: "}" > +| < LBRACKET: "[" > +| < RBRACKET: "]" > +| < SEMICOLON: ";" > +| < COMMA: "," > +| < DOT: "." > +| < AT: "@" > +| < DOLLAR: "$" > +} + +/* OPERATORS */ + +TOKEN : +{ + + < EQ: "=" > +| < LT: "<" > +| < GT: ">" > +| < BANG: "!" > +| < TILDE: "~" > +| < HOOK: "?" > +| < COLON: ":" > +| < LE: "<=" > +| < GE: ">=" > +| < NE: "!=" > +| < NEQ: "<>" > +| < SC_OR: "||" > +| < SC_AND: "&&" > +| < INCR: "++" > +| < DECR: "--" > +| < PLUS: "+" > +| < MINUS: "-" > +| < STAR: "*" > +| < SLASH: "/" > +| < BIT_AND: "&" > +| < BIT_OR: "|" > +| < XOR: "^" > +| < REM: "%" > +| < LSHIFT: "<<" > +| < PLUSASSIGN: "+=" > +| < MINUSASSIGN: "-=" > +| < STARASSIGN: "*=" > +| < SLASHASSIGN: "/=" > +| < ANDASSIGN: "&=" > +| < ORASSIGN: "|=" > +| < XORASSIGN: "^=" > +| < REMASSIGN: "%=" > +| < LSHIFTASSIGN: "<<=" > +| < RSIGNEDSHIFTASSIGN: ">>=" > +| < RUNSIGNEDSHIFTASSIGN: ">>>=" > +| < ELLIPSIS: "..." > +| < RANGE: ".." > +| < NOT: ( "N" | "n") ( "O" | "o") ( "T" | "t") > +| < IN: ( "I" | "i") ( "N" | "n") > +| < LIKE: ( "L" | "l") ( "I" | "i") ( "K" | "k") ( "E" | "e") > +| < IS: "is" | "IS" | "Is" | "iS" > +| < BETWEEN: ( "B" | "b") ( "E" | "e") ( "T" | "t") ( "W" | "w") ( "E" | "e") ( "E" | "e") ( "N" | "n")> +| < CONTAINS: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) > +| < CONTAINSALL: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "A" | "a" ) ( "L" | "l" ) ( "L" | "l" ) > +| < CONTAINSKEY: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "K" | "k" ) ( "E" | "e" ) ( "Y" | "y" ) > +| < CONTAINSVALUE: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "V" | "v" ) ( "A" | "a" ) ( "L" | "l" ) ( "U" | "u" ) ( "E" | "e" ) > +| < CONTAINSTEXT: ( "C" | "c" ) ( "O" | "o" ) ( "N" | "n" ) ( "T" | "t" ) ( "A" | "a" ) ( "I" | "i" ) ( "N" | "n" ) ( "S" | "s" ) ( "T" | "t" ) ( "E" | "e" ) ( "X" | "x" ) ( "T" | "t" ) > +| < MATCHES: ( "M" | "m") ( "A" | "a") ( "T" | "t") ( "C" | "c") ( "H" | "h") ( "E" | "e") ( "S" | "s") > +| < KEY: ( "K" | "k") ( "E" | "e") ( "Y" | "y") > +| < INSTANCEOF: "instanceof" > +| < CLUSTER: "cluster" > +} + + + +TOKEN : +{ + < IDENTIFIER: ( ()? ()* ) > +| + < INDEX_IDENTIFIER: "index:" ( "__@recordmap@___" )? ( ( | ) )* > +| + < INDEXVALUES_IDENTIFIER: "indexvalues:" ( ( | ) )* > +| + < INDEXVALUESASC_IDENTIFIER: "indexvaluesasc:" ( ( | ) )* > +| + < INDEXVALUESDESC_IDENTIFIER: "indexvaluesdesc:" ( ( | ) )* > +| + < CLUSTER_IDENTIFIER: > +| + < METADATA_IDENTIFIER: "metadata:" > +| + < #LETTER: + [ "A"-"Z", + "_", + "a"-"z" + ] + > +| + < #PART_LETTER: + [ "0"-"9", + "A"-"Z", + "_", + "a"-"z" + ] + > +} + +ORid Rid(): +{/*@bgen(jjtree) Rid */ + ORid jjtn000 = new ORid(JJTRID); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) Rid */ + try { +/*@egen*/ + ( + LOOKAHEAD(4) + "#" jjtn000.cluster = Integer() jjtn000.position = Integer() + | + LOOKAHEAD(3) + jjtn000.cluster = Integer() jjtn000.position = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +/** Root production. */ +OStatement parse() : +{/*@bgen(jjtree) parse */ + Oparse jjtn000 = new Oparse(JJTPARSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/OStatement result;} +{/*@bgen(jjtree) parse */ + try { +/*@egen*/ + result = Statement() /*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return result; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OIdentifier Identifier(): +{/*@bgen(jjtree) Identifier */ + OIdentifier jjtn000 = new OIdentifier(JJTIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/Token token;} +{/*@bgen(jjtree) Identifier */ +try { +/*@egen*/ +( + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = + | + token = +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { jjtn000.value = token.image; return jjtn000; }/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OInteger Integer(): +{/*@bgen(jjtree) Integer */ + OInteger jjtn000 = new OInteger(JJTINTEGER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + int sign = 1; + Token tokenVal; +} +{/*@bgen(jjtree) Integer */ +try { +/*@egen*/ +( + [ {sign = -1;} ] tokenVal = {jjtn000.value = sign * Integer.parseInt(tokenVal.image);} +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return jjtn000; }/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + + +OFloatingPoint FloatingPoint(): +{/*@bgen(jjtree) FloatingPoint */ + OFloatingPoint jjtn000 = new OFloatingPoint(JJTFLOATINGPOINT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + String stringValue; + Token tokenVal; +} +{/*@bgen(jjtree) FloatingPoint */ + try { +/*@egen*/ + ( + [ { jjtn000.sign = -1; } ] tokenVal = { jjtn000.stringValue = tokenVal.image; } + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +ONumber Number(): +{/*@bgen(jjtree) Number */ + ONumber jjtn000 = new ONumber(JJTNUMBER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ ONumber result; } +{/*@bgen(jjtree) Number */ + try { +/*@egen*/ + ( + LOOKAHEAD( Integer() ) + result = Integer() + | + LOOKAHEAD( FloatingPoint() ) + result = FloatingPoint() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return result; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OStatement Statement(): +{/*@bgen(jjtree) Statement */ + OStatement jjtn000 = new OStatement(JJTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OStatement result = null;} +{/*@bgen(jjtree) Statement */ + try { +/*@egen*/ + ( + LOOKAHEAD( SelectStatement() ) + result = SelectStatement() + | + result = SelectWithoutTargetStatement() + | + result = TraverseStatement() + | + LOOKAHEAD(2) + result = DeleteStatement() + | + LOOKAHEAD(2) + result = DeleteVertexStatement() + | + LOOKAHEAD(2) + result = DeleteEdgeStatement() + | + result = InsertStatement() + | + LOOKAHEAD(CreateVertexStatementNoTarget()) + result = CreateVertexStatementNoTarget() + | + LOOKAHEAD(CreateVertexStatement()) + result = CreateVertexStatement() + | + LOOKAHEAD(CreateVertexStatementEmpty()) + result = CreateVertexStatementEmpty() + | + LOOKAHEAD(CreateVertexStatementEmptyNoTarget()) + result = CreateVertexStatementEmptyNoTarget() + | + LOOKAHEAD(2) + result = CreateEdgeStatement() + | + result = UpdateStatement() + + ) [ ]/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return result; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OSelectWithoutTargetStatement SelectWithoutTargetStatement(): +{/*@bgen(jjtree) SelectWithoutTargetStatement */ + OSelectWithoutTargetStatement jjtn000 = new OSelectWithoutTargetStatement(JJTSELECTWITHOUTTARGETSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) SelectWithoutTargetStatement */ + try { +/*@egen*/ + ( + + [ jjtn000.projection = Projection() ] + + jjtn000.target = FromClause() + [ jjtn000.letClause = LetClause() ] + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.groupBy = GroupBy() ] + [ jjtn000.orderBy = OrderBy() ] + ( + [ + jjtn000.skip = Skip() [ jjtn000.limit = Limit() ] + | + jjtn000.limit = Limit() [ jjtn000.skip = Skip() ] + ] + ) + [ jjtn000.fetchPlan = FetchPlan() ] + [ jjtn000.timeout = Timeout() ] + [ {jjtn000.lockRecord = true;} ] + [ { jjtn000.parallel = true; } ] + [ { jjtn000.noCache = true; } ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OTraverseStatement TraverseStatement(): +{/*@bgen(jjtree) TraverseStatement */ + OTraverseStatement jjtn000 = new OTraverseStatement(JJTTRAVERSESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OTraverseProjectionItem lastProjection;} +{/*@bgen(jjtree) TraverseStatement */ + try { +/*@egen*/ + ( + + [ + lastProjection = TraverseProjectionItem() { jjtn000.projections.add(lastProjection); } + ( lastProjection = TraverseProjectionItem() { jjtn000.projections.add(lastProjection); } )* + ] + + jjtn000.target = FromClause() + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Limit() ] + [ + ( + { jjtn000.strategy = OTraverseStatement.Strategy.DEPTH_FIRST; } + | + { jjtn000.strategy = OTraverseStatement.Strategy.BREADTH_FIRST; } + ) + ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +ODeleteStatement DeleteStatement(): +{/*@bgen(jjtree) DeleteStatement */ + ODeleteStatement jjtn000 = new ODeleteStatement(JJTDELETESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) DeleteStatement */ +try { +/*@egen*/ +( + + + jjtn000.fromClause = FromClause() + [ { jjtn000.returnBefore = true; } ] + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Integer() ] + [ { jjtn000.unsafe = true; }] +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +ODeleteVertexStatement DeleteVertexStatement(): +{/*@bgen(jjtree) DeleteVertexStatement */ + ODeleteVertexStatement jjtn000 = new ODeleteVertexStatement(JJTDELETEVERTEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) DeleteVertexStatement */ +try { +/*@egen*/ +( + + + jjtn000.fromClause = FromClause() + [ { jjtn000.returnBefore = true; } ] + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Integer() ] +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +ODeleteEdgeStatement DeleteEdgeStatement(): +{/*@bgen(jjtree) DeleteEdgeStatement */ + ODeleteEdgeStatement jjtn000 = new ODeleteEdgeStatement(JJTDELETEEDGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ ODeleteEdgeStatement result; } +{/*@bgen(jjtree) DeleteEdgeStatement */ + try { +/*@egen*/ + ( + LOOKAHEAD(DeleteEdgeByRidStatement()) + result = DeleteEdgeByRidStatement() + | + LOOKAHEAD(DeleteEdgeFromToStatement()) + result = DeleteEdgeFromToStatement() + | + LOOKAHEAD(DeleteEdgeVToStatement()) + result = DeleteEdgeVToStatement() + | + LOOKAHEAD(DeleteEdgeToStatement()) + result = DeleteEdgeToStatement() + | + LOOKAHEAD(DeleteEdgeWhereStatement()) + result = DeleteEdgeWhereStatement() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return result;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +ODeleteEdgeStatement DeleteEdgeByRidStatement(): +{/*@bgen(jjtree) DeleteEdgeByRidStatement */ + ODeleteEdgeByRidStatement jjtn000 = new ODeleteEdgeByRidStatement(JJTDELETEEDGEBYRIDSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) DeleteEdgeByRidStatement */ +try { +/*@egen*/ +( + + + ( + jjtn000.rid = Rid() + | + ( + + + [ + lastRid = Rid() + { + jjtn000.rids = new ArrayList(); + jjtn000.rids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.rids.add(lastRid); } + )* + ] + ) + ) + + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + + +ODeleteEdgeStatement DeleteEdgeFromToStatement(): +{/*@bgen(jjtree) DeleteEdgeFromToStatement */ + ODeleteEdgeFromToStatement jjtn000 = new ODeleteEdgeFromToStatement(JJTDELETEEDGEFROMTOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) DeleteEdgeFromToStatement */ +try { +/*@egen*/ +( + + + + [ jjtn000.className = Identifier() ] + + + + ( + jjtn000.leftRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.leftRids=new ArrayList(); + jjtn000.leftRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.leftRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.leftStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.leftStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.leftParam = InputParameter() + | + jjtn000.leftIdentifier = Identifier() + ) + + [ + + ( + jjtn000.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.rightParam = InputParameter() + | + jjtn000.rightIdentifier = Identifier() + ) + ] + + + + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Limit() ] + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +ODeleteEdgeStatement DeleteEdgeToStatement(): +{/*@bgen(jjtree) DeleteEdgeToStatement */ + ODeleteEdgeToStatement jjtn000 = new ODeleteEdgeToStatement(JJTDELETEEDGETOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) DeleteEdgeToStatement */ + try { +/*@egen*/ + ( + + + + jjtn000.className = Identifier() + + + ( + jjtn000.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.rightParam = InputParameter() + | + jjtn000.rightIdentifier = Identifier() + ) + + + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Limit() ] + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +ODeleteEdgeStatement DeleteEdgeVToStatement(): +{/*@bgen(jjtree) DeleteEdgeVToStatement */ + ODeleteEdgeVToStatement jjtn000 = new ODeleteEdgeVToStatement(JJTDELETEEDGEVTOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) DeleteEdgeVToStatement */ + try { +/*@egen*/ + ( + + + + + ( + jjtn000.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.rightParam = InputParameter() + | + jjtn000.rightIdentifier = Identifier() + ) + + + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Limit() ] + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +ODeleteEdgeStatement DeleteEdgeWhereStatement(): +{/*@bgen(jjtree) DeleteEdgeWhereStatement */ + ODeleteEdgeWhereStatement jjtn000 = new ODeleteEdgeWhereStatement(JJTDELETEEDGEWHERESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) DeleteEdgeWhereStatement */ + try { +/*@egen*/ + ( + + + + [ jjtn000.className = Identifier() ] + + [ jjtn000.whereClause = WhereClause() ] + [ jjtn000.limit = Limit() ] + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OUpdateStatement UpdateStatement(): +{/*@bgen(jjtree) UpdateStatement */ + OUpdateStatement jjtn000 = new OUpdateStatement(JJTUPDATESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OUpdateOperations lastOperations; } +{/*@bgen(jjtree) UpdateStatement */ + try { +/*@egen*/ + ( + + ( + jjtn000.targetRid = Rid() + | + jjtn000.targetClass = Identifier() + | + jjtn000.targetCluster = Cluster() + | + jjtn000.targetIndex = IndexIdentifier() + ) + ( lastOperations = UpdateOperations() { jjtn000.operations.add(lastOperations); } )+ + [ { jjtn000.upsert = true; } ] + [ + + ( { jjtn000.returnBefore = true; } | { jjtn000.returnAfter = true; } ) + [ + jjtn000.returnProjection = Projection() + ] + ] + [ jjtn000.whereClause = WhereClause() ] + [ { jjtn000.lockRecord = true; } ] + [ jjtn000.limit = Limit() ] + [ jjtn000.timeout = Timeout() ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OUpdateOperations UpdateOperations(): +{/*@bgen(jjtree) UpdateOperations */ + OUpdateOperations jjtn000 = new OUpdateOperations(JJTUPDATEOPERATIONS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OUpdateItem lastItem; + OUpdatePutItem lastPutItem; + OUpdateIncrementItem lastIncrementItem; + OUpdateRemoveItem lastRemoveItem; +} +{/*@bgen(jjtree) UpdateOperations */ + try { +/*@egen*/ + ( + ( + { jjtn000.type = OUpdateOperations.TYPE_SET; } + lastItem = UpdateItem() { jjtn000.updateItems.add(lastItem); } + ( + lastItem = UpdateItem() { jjtn000.updateItems.add(lastItem); } + )* + ) + | + ( + { jjtn000.type = OUpdateOperations.TYPE_PUT; } + lastPutItem = UpdatePutItem() { jjtn000.updatePutItems.add(lastPutItem); } + ( + lastPutItem = UpdatePutItem() { jjtn000.updatePutItems.add(lastPutItem); } + )* + ) + | + ( + ( + { jjtn000.type = OUpdateOperations.TYPE_MERGE; } + | + { jjtn000.type = OUpdateOperations.TYPE_CONTENT; } + ) + jjtn000.json = Json() + ) + | + ( + ( + { jjtn000.type = OUpdateOperations.TYPE_INCREMENT; } + | + { jjtn000.type = OUpdateOperations.TYPE_ADD; } + ) + lastIncrementItem = UpdateIncrementItem() { jjtn000.updateIncrementItems.add(lastIncrementItem); } + ( + lastIncrementItem = UpdateIncrementItem() { jjtn000.updateIncrementItems.add(lastIncrementItem); } + )* + ) + | + ( + { jjtn000.type = OUpdateOperations.TYPE_REMOVE; } + lastRemoveItem = UpdateRemoveItem() { jjtn000.updateRemoveItems.add(lastRemoveItem); } + ( + + lastRemoveItem = UpdateRemoveItem() { jjtn000.updateRemoveItems.add(lastRemoveItem); } + )* + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OUpdateItem UpdateItem(): +{/*@bgen(jjtree) UpdateItem */ + OUpdateItem jjtn000 = new OUpdateItem(JJTUPDATEITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) UpdateItem */ +try { +/*@egen*/ +( + jjtn000.left = Identifier() + ( + { jjtn000.operator = OUpdateItem.OPERATOR_EQ; } + | + { jjtn000.operator = OUpdateItem.OPERATOR_PLUSASSIGN; } + | + { jjtn000.operator = OUpdateItem.OPERATOR_MINUSASSIGN; } + | + { jjtn000.operator = OUpdateItem.OPERATOR_STARASSIGN; } + | + { jjtn000.operator = OUpdateItem.OPERATOR_SLASHASSIGN; } + ) + jjtn000.right = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return jjtn000; }/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OUpdateIncrementItem UpdateIncrementItem(): +{/*@bgen(jjtree) UpdateIncrementItem */ + OUpdateIncrementItem jjtn000 = new OUpdateIncrementItem(JJTUPDATEINCREMENTITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) UpdateIncrementItem */ + try { +/*@egen*/ + ( + jjtn000.left = Identifier() jjtn000.right = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OUpdateRemoveItem UpdateRemoveItem(): +{/*@bgen(jjtree) UpdateRemoveItem */ + OUpdateRemoveItem jjtn000 = new OUpdateRemoveItem(JJTUPDATEREMOVEITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) UpdateRemoveItem */ + try { +/*@egen*/ + ( + jjtn000.left = Identifier() [ jjtn000.right = Expression() ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OUpdatePutItem UpdatePutItem(): +{/*@bgen(jjtree) UpdatePutItem */ + OUpdatePutItem jjtn000 = new OUpdatePutItem(JJTUPDATEPUTITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) UpdatePutItem */ + try { +/*@egen*/ + ( + jjtn000.left = Identifier() jjtn000.key = Expression() jjtn000.value = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OUpdateAddItem UpdateAddItem(): +{/*@bgen(jjtree) UpdateAddItem */ + OUpdateAddItem jjtn000 = new OUpdateAddItem(JJTUPDATEADDITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) UpdateAddItem */ + try { +/*@egen*/ + ( + jjtn000.left = Identifier() + jjtn000.right = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OInsertStatement InsertStatement(): +{/*@bgen(jjtree) InsertStatement */ + OInsertStatement jjtn000 = new OInsertStatement(JJTINSERTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) InsertStatement */ +try { +/*@egen*/ +( + + + ( + jjtn000.targetClass = Identifier() [ jjtn000.targetClusterName = Identifier()] + | + jjtn000.targetCluster = Cluster() + | + jjtn000.targetIndex = IndexIdentifier() + ) + [ jjtn000.returnStatement = Projection() ] + jjtn000.insertBody = InsertBody() + [ { jjtn000.unsafe = true; }] +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OInsertBody InsertBody(): +{/*@bgen(jjtree) InsertBody */ + OInsertBody jjtn000 = new OInsertBody(JJTINSERTBODY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OIdentifier lastIdentifier; + OExpression lastExpression; + List lastExpressionList; +} +{/*@bgen(jjtree) InsertBody */ + try { +/*@egen*/ + ( + ( + LOOKAHEAD(3) + ( + + lastIdentifier = Identifier() + { + jjtn000.identifierList = new ArrayList(); + jjtn000.identifierList.add(lastIdentifier); + } + ( + + lastIdentifier = Identifier() { jjtn000.identifierList.add(lastIdentifier); } + )* + + + + { + jjtn000.valueExpressions = new ArrayList>(); + lastExpressionList = new ArrayList(); + jjtn000.valueExpressions.add(lastExpressionList); + } + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + ( + + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + )* + + ( + + + { + lastExpressionList = new ArrayList(); + jjtn000.valueExpressions.add(lastExpressionList); + } + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + ( + + lastExpression = Expression() { lastExpressionList.add(lastExpression); } + )* + + )* + ) + | + LOOKAHEAD(3) + ( + + { + jjtn000.setExpressions = new ArrayList(); + OInsertSetExpression lastSetExpr = new OInsertSetExpression(); + jjtn000.setExpressions.add(lastSetExpr); + } + lastSetExpr.left = Identifier() lastSetExpr.right = Expression() + + ( + + { + lastSetExpr = new OInsertSetExpression(); + jjtn000.setExpressions.add(lastSetExpr); + } + lastSetExpr.left = Identifier() lastSetExpr.right = Expression() + )* + ) + | + ( + [ ] + ( + jjtn000.selectStatement = SelectStatement() + | + LOOKAHEAD(2) + ( + jjtn000.selectStatement = SelectStatement() { jjtn000.selectInParentheses = true; } + ) + ) + ) + | + ( jjtn000.content = Json() ) + ) + [ jjtn000.returnProjection = Projection() ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OCreateVertexStatementEmptyNoTarget CreateVertexStatementEmptyNoTarget(): +{/*@bgen(jjtree) CreateVertexStatementEmptyNoTarget */ + OCreateVertexStatementEmptyNoTarget jjtn000 = new OCreateVertexStatementEmptyNoTarget(JJTCREATEVERTEXSTATEMENTEMPTYNOTARGET); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) CreateVertexStatementEmptyNoTarget */ + try { +/*@egen*/ + + /*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OCreateVertexStatementEmpty CreateVertexStatementEmpty(): +{/*@bgen(jjtree) CreateVertexStatementEmpty */ + OCreateVertexStatementEmpty jjtn000 = new OCreateVertexStatementEmpty(JJTCREATEVERTEXSTATEMENTEMPTY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) CreateVertexStatementEmpty */ + try { +/*@egen*/ + + + + jjtn000.targetClass = Identifier() + [ + + jjtn000.targetClusterName = Identifier() + ]/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OCreateVertexStatement CreateVertexStatement(): +{/*@bgen(jjtree) CreateVertexStatement */ + OCreateVertexStatement jjtn000 = new OCreateVertexStatement(JJTCREATEVERTEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) CreateVertexStatement */ +try { +/*@egen*/ +( + + + ( + LOOKAHEAD( Identifier() ) + ( + jjtn000.targetClass = Identifier() + [ + + jjtn000.targetClusterName = Identifier() + ] + ) + | + LOOKAHEAD( Cluster() ) + jjtn000.targetCluster = Cluster() + ) + [ jjtn000.returnStatement = Projection() ] + [ LOOKAHEAD(InsertBody()) jjtn000.insertBody = InsertBody() ] +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OCreateVertexStatementNoTarget CreateVertexStatementNoTarget(): +{/*@bgen(jjtree) CreateVertexStatementNoTarget */ + OCreateVertexStatementNoTarget jjtn000 = new OCreateVertexStatementNoTarget(JJTCREATEVERTEXSTATEMENTNOTARGET); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) CreateVertexStatementNoTarget */ +try { +/*@egen*/ +( + + + jjtn000.insertBody = InsertBody() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OCreateEdgeStatement CreateEdgeStatement(): +{/*@bgen(jjtree) CreateEdgeStatement */ + OCreateEdgeStatement jjtn000 = new OCreateEdgeStatement(JJTCREATEEDGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + ORid lastRid; +} +{/*@bgen(jjtree) CreateEdgeStatement */ +try { +/*@egen*/ +( + + + [ jjtn000.targetClass = Identifier() [ jjtn000.targetClusterName = Identifier()]] + + ( + jjtn000.leftRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.leftRids=new ArrayList(); + jjtn000.leftRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.leftRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.leftStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.leftStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.leftParam = InputParameter() + | + jjtn000.leftIdentifier = Identifier() + ) + + ( + jjtn000.rightRid = Rid() + | + ( + + [ + lastRid = Rid() + { + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + } + ( + + lastRid = Rid() { jjtn000.rightRids.add(lastRid); } + )* + ] + ) + | + ( + + ( + LOOKAHEAD(SelectStatement()) jjtn000.rightStatement = SelectStatement() + | + LOOKAHEAD(SelectWithoutTargetStatement()) jjtn000.rightStatement = SelectWithoutTargetStatement() + ) + + ) + | + jjtn000.rightParam = InputParameter() + | + jjtn000.rightIdentifier = Identifier() + ) + [ jjtn000.body = InsertBody() ] + [ jjtn000.retry = Retry() ] + [ jjtn000.wait = Wait() ] +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OInputParameter InputParameter(): +{/*@bgen(jjtree) InputParameter */ + OInputParameter jjtn000 = new OInputParameter(JJTINPUTPARAMETER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInputParameter result; } +{/*@bgen(jjtree) InputParameter */ + try { +/*@egen*/ + ( + result = PositionalParameter() + | + result = NamedParameter() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return result; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OPositionalParameter PositionalParameter(): +{/*@bgen(jjtree) PositionalParameter */ + OPositionalParameter jjtn000 = new OPositionalParameter(JJTPOSITIONALPARAMETER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) PositionalParameter */ + try { +/*@egen*/ + /*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + jjtn000.paramNumber = inputParamCount; + inputParamCount++; + return jjtn000; + }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +ONamedParameter NamedParameter(): +{/*@bgen(jjtree) NamedParameter */ + ONamedParameter jjtn000 = new ONamedParameter(JJTNAMEDPARAMETER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) NamedParameter */ + try { +/*@egen*/ + ( + jjtn000.paramName = Identifier() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + jjtn000.paramNumber = inputParamCount; + inputParamCount++; + return jjtn000; + }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OProjection Projection(): +{/*@bgen(jjtree) Projection */ + OProjection jjtn000 = new OProjection(JJTPROJECTION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + java.util.List items = new java.util.ArrayList(); + OProjectionItem lastItem = null; +} +{/*@bgen(jjtree) Projection */ + try { +/*@egen*/ + ( + lastItem = ProjectionItem() {items.add(lastItem);} ( "," lastItem = ProjectionItem() {items.add(lastItem);} )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + jjtn000.items = items; + return jjtn000; + }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OProjectionItem ProjectionItem(): +{/*@bgen(jjtree) ProjectionItem */ + OProjectionItem jjtn000 = new OProjectionItem(JJTPROJECTIONITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ProjectionItem */ +try { +/*@egen*/ +( + "*" {jjtn000.all = true;} + | + ( + jjtn000.expression = Expression() + [ jjtn000.alias = Alias() ] + ) +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OArraySelector ArraySelector(): +{/*@bgen(jjtree) ArraySelector */ + OArraySelector jjtn000 = new OArraySelector(JJTARRAYSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ArraySelector */ + try { +/*@egen*/ + ( + LOOKAHEAD( Rid() ) + jjtn000.rid = Rid() + | + LOOKAHEAD( InputParameter() ) + jjtn000.inputParam = InputParameter() + | + LOOKAHEAD( Expression() ) + jjtn000.expression = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OArrayNumberSelector ArrayNumberSelector(): +{/*@bgen(jjtree) ArrayNumberSelector */ + OArrayNumberSelector jjtn000 = new OArrayNumberSelector(JJTARRAYNUMBERSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ Token tokenVal; } +{/*@bgen(jjtree) ArrayNumberSelector */ + try { +/*@egen*/ + ( + LOOKAHEAD( InputParameter() ) + jjtn000.inputValue = InputParameter() + | + LOOKAHEAD( Integer() ) + tokenVal = { jjtn000.integer = Integer.parseInt(tokenVal.image); } + /* TODO for 3.0 + | + LOOKAHEAD( MathExpression() ) + jjtThis.expressionValue = MathExpression() + */ + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OArraySingleValuesSelector ArraySingleValuesSelector(): +{/*@bgen(jjtree) ArraySingleValuesSelector */ + OArraySingleValuesSelector jjtn000 = new OArraySingleValuesSelector(JJTARRAYSINGLEVALUESSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OArraySelector lastSelector; } +{/*@bgen(jjtree) ArraySingleValuesSelector */ + try { +/*@egen*/ + ( + lastSelector = ArraySelector() { jjtn000.items.add(lastSelector); } + ( lastSelector = ArraySelector() { jjtn000.items.add(lastSelector); } ) * + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OArrayRangeSelector ArrayRangeSelector(): +{/*@bgen(jjtree) ArrayRangeSelector */ + OArrayRangeSelector jjtn000 = new OArrayRangeSelector(JJTARRAYRANGESELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ Token token; } +{/*@bgen(jjtree) ArrayRangeSelector */ + try { +/*@egen*/ + ( + + /* TODO for 3.0 + token = + { + String img = token.image; + String[] splitted = img.split(".."); + jjtThis.from = Integer.parseInt(splitted[0], 10); + jjtThis.to = Integer.parseInt(splitted[1], 10); + } + | + */ + ( + jjtn000.fromSelector = ArrayNumberSelector() [ | { jjtn000.newRange = true; } ] jjtn000.toSelector = ArrayNumberSelector() + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +String Alias(): +{/*@bgen(jjtree) Alias */ + OAlias jjtn000 = new OAlias(JJTALIAS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OIdentifier identifier; } +{/*@bgen(jjtree) Alias */ + try { +/*@egen*/ + identifier = Identifier()/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return identifier.getValue();}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +ORecordAttribute RecordAttribute(): +{/*@bgen(jjtree) RecordAttribute */ + ORecordAttribute jjtn000 = new ORecordAttribute(JJTRECORDATTRIBUTE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ Token token; } +{/*@bgen(jjtree) RecordAttribute */ + try { +/*@egen*/ + ( + token = { jjtn000.name = token.image; } + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OFunctionCall FunctionCall(): +{/*@bgen(jjtree) FunctionCall */ + OFunctionCall jjtn000 = new OFunctionCall(JJTFUNCTIONCALL); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OExpression lastExpression = null; +} +{/*@bgen(jjtree) FunctionCall */ + try { +/*@egen*/ + ( + ( + jjtn000.name = Identifier() + ) + + ( + { jjtn000.star = true;} + | + ( + [ + lastExpression = Expression() {jjtn000.params.add(lastExpression);} ( lastExpression = Expression() {jjtn000.params.add(lastExpression);})* + ] + ) + ) + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OMethodCall MethodCall(): +{/*@bgen(jjtree) MethodCall */ + OMethodCall jjtn000 = new OMethodCall(JJTMETHODCALL); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OExpression lastExpression; } +{/*@bgen(jjtree) MethodCall */ + try { +/*@egen*/ + ( + jjtn000.methodName = Identifier() + [ + lastExpression = Expression() { jjtn000.params.add(lastExpression); } + ( lastExpression = Expression() { jjtn000.params.add(lastExpression); } )* + ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OLevelZeroIdentifier LevelZeroIdentifier(): +{/*@bgen(jjtree) LevelZeroIdentifier */ + OLevelZeroIdentifier jjtn000 = new OLevelZeroIdentifier(JJTLEVELZEROIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) LevelZeroIdentifier */ + try { +/*@egen*/ + ( + LOOKAHEAD( FunctionCall() ) + jjtn000.functionCall = FunctionCall() + | + { jjtn000.self = true; } + | + LOOKAHEAD( Collection() ) + jjtn000.collection = Collection() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OSuffixIdentifier SuffixIdentifier(): +{/*@bgen(jjtree) SuffixIdentifier */ + OSuffixIdentifier jjtn000 = new OSuffixIdentifier(JJTSUFFIXIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) SuffixIdentifier */ + try { +/*@egen*/ + ( + LOOKAHEAD( Identifier() ) + jjtn000.identifier = Identifier() + | + LOOKAHEAD( RecordAttribute() ) + jjtn000.recordAttribute = RecordAttribute() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OBaseIdentifier BaseIdentifier(): +{/*@bgen(jjtree) BaseIdentifier */ + OBaseIdentifier jjtn000 = new OBaseIdentifier(JJTBASEIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) BaseIdentifier */ + try { +/*@egen*/ + ( + LOOKAHEAD( LevelZeroIdentifier() ) + jjtn000.levelZero = LevelZeroIdentifier() + | + LOOKAHEAD( SuffixIdentifier() ) + jjtn000.suffix = SuffixIdentifier() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OModifier Modifier(): +{/*@bgen(jjtree) Modifier */ + OModifier jjtn000 = new OModifier(JJTMODIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) Modifier */ + try { +/*@egen*/ + ( + ( + ( + { jjtn000.squareBrackets = true; } + ( + LOOKAHEAD( ArrayRangeSelector() ) + jjtn000.arrayRange = ArrayRangeSelector() + | + LOOKAHEAD( OrBlock() ) + jjtn000.condition = OrBlock() + | + LOOKAHEAD( ArraySingleValuesSelector() ) + jjtn000.arraySingleValues = ArraySingleValuesSelector() + ) + + ) + | + LOOKAHEAD( MethodCall() ) + jjtn000.methodCall = MethodCall() + | + jjtn000.suffix = SuffixIdentifier() + ) + [ + LOOKAHEAD( Modifier() ) + jjtn000.next = Modifier() + ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OExpression Expression(): +{/*@bgen(jjtree) Expression */ + OExpression jjtn000 = new OExpression(JJTEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/Token token; } +{/*@bgen(jjtree) Expression */ + try { +/*@egen*/ + ( + {jjtn000.value = null;} + | + LOOKAHEAD(2) + token = + { + jjtn000.value = token.image.substring(1, token.image.length() - 1); + if(token.image.startsWith("'")) { + jjtn000.singleQuotes = true; + }else{ + jjtn000.doubleQuotes = true; + } + + } + + | + token = {jjtn000.value = token.image.substring(1, token.image.length() - 1); jjtn000.singleQuotes = true;} + | + LOOKAHEAD( Rid() ) + jjtn000.value = Rid() + | + LOOKAHEAD( InputParameter() ) + jjtn000.value = InputParameter() + | + jjtn000.value = MathExpression() + | + jjtn000.value = Json() + | + {jjtn000.value = true;} + | + {jjtn000.value = false;} + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OMathExpression MathExpression(): +{/*@bgen(jjtree) MathExpression */ + OMathExpression jjtn000 = new OMathExpression(JJTMATHEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OMathExpression sub; + jjtn000.setChildExpressions(new java.util.ArrayList()); +} +{/*@bgen(jjtree) MathExpression */ + try { +/*@egen*/ + ( + sub = MultExpression() { jjtn000.getChildExpressions().add(sub); } + ( + LOOKAHEAD( 2 ) ( { jjtn000.operators.add( OMathExpression.Operator.PLUS); } | { jjtn000.operators.add(OMathExpression.Operator.MINUS); }) + sub = MultExpression() { jjtn000.getChildExpressions().add(sub); } + )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + if(jjtn000.getChildExpressions().size() != 1){ + return jjtn000; + }else{ + return jjtn000.getChildExpressions().get(0); + } + }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OMathExpression MultExpression(): +{/*@bgen(jjtree) MultExpression */ + OMultExpression jjtn000 = new OMultExpression(JJTMULTEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OMathExpression sub; + jjtn000.setChildExpressions(new java.util.ArrayList()); +} +{/*@bgen(jjtree) MultExpression */ + try { +/*@egen*/ + ( + sub = FirstLevelExpression() { jjtn000.getChildExpressions().add(sub); } + ( + LOOKAHEAD( 2 ) + ( + { jjtn000.operators.add( OMathExpression.Operator.STAR); } + | + { jjtn000.operators.add( OMathExpression.Operator.SLASH); } + | + { jjtn000.operators.add( OMathExpression.Operator.REM); } + ) + sub = FirstLevelExpression() { jjtn000.getChildExpressions().add(sub); } + )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + if(jjtn000.getChildExpressions().size() != 1){ + return jjtn000; + }else{ + return jjtn000.getChildExpressions().get(0); + } + }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OMathExpression FirstLevelExpression(): +{/*@bgen(jjtree) FirstLevelExpression */ + OFirstLevelExpression jjtn000 = new OFirstLevelExpression(JJTFIRSTLEVELEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OMathExpression expr;} +{/*@bgen(jjtree) FirstLevelExpression */ + try { +/*@egen*/ + ( + LOOKAHEAD( ParenthesisExpression() ) + expr = ParenthesisExpression() + | + LOOKAHEAD( BaseExpression() ) + expr = BaseExpression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return expr;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OMathExpression ParenthesisExpression(): +{/*@bgen(jjtree) ParenthesisExpression */ + OParenthesisExpression jjtn000 = new OParenthesisExpression(JJTPARENTHESISEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ParenthesisExpression */ + try { +/*@egen*/ + ( + ( jjtn000.expression = Expression() | jjtn000.statement = SelectStatement() ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBaseExpression BaseExpression(): +{/*@bgen(jjtree) BaseExpression */ + OBaseExpression jjtn000 = new OBaseExpression(JJTBASEEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) BaseExpression */ + try { +/*@egen*/ + ( + jjtn000.number = Number() + | + ( + jjtn000.identifier = BaseIdentifier() + [ + LOOKAHEAD( Modifier() ) + jjtn000.modifier = Modifier() + ] + ) + | + ( + jjtn000.inputParam = InputParameter() + [ + LOOKAHEAD( Modifier() ) + jjtn000.modifier = Modifier() + ] + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + + +OFromClause FromClause(): +{/*@bgen(jjtree) FromClause */ + OFromClause jjtn000 = new OFromClause(JJTFROMCLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) FromClause */ + try { +/*@egen*/ + jjtn000.item = FromItem()/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OLetClause LetClause(): +{/*@bgen(jjtree) LetClause */ + OLetClause jjtn000 = new OLetClause(JJTLETCLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OLetItem lastItem; +} +{/*@bgen(jjtree) LetClause */ + try { +/*@egen*/ + ( + lastItem = LetItem() { jjtn000.items.add(lastItem); } ( lastItem = LetItem() { jjtn000.items.add(lastItem); } )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OLetItem LetItem(): +{/*@bgen(jjtree) LetItem */ + OLetItem jjtn000 = new OLetItem(JJTLETITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ } +{/*@bgen(jjtree) LetItem */ + try { +/*@egen*/ + ( + jjtn000.varName = Identifier() + ( + LOOKAHEAD( Expression() ) + jjtn000.expression = Expression() + | + ( + + ( + jjtn000.query = SelectStatement() + | + jjtn000.query = SelectWithoutTargetStatement() + | + jjtn000.query = TraverseStatement() + ) + + ) + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +OFromItem FromItem(): +{/*@bgen(jjtree) FromItem */ + OFromItem jjtn000 = new OFromItem(JJTFROMITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + jjtn000.rids = new java.util.ArrayList(); + ORid lastRid; +} +{/*@bgen(jjtree) FromItem */ + try { +/*@egen*/ + ( + lastRid = Rid() { jjtn000.rids.add(lastRid); } + | + /*( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + ( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + )* + ) + |*/ + jjtn000.cluster = Cluster() + | + jjtn000.index = IndexIdentifier() + | + jjtn000.metadata = MetadataIdentifier() + | + /*jjtThis.className = Identifier() + |*/ + ( jjtn000.statement = SelectStatement() | jjtn000.statement = TraverseStatement() ) + | + jjtn000.inputParam = InputParameter() + | + ( + jjtn000.identifier = BaseIdentifier() + [ + LOOKAHEAD( Modifier() ) + jjtn000.modifier = Modifier() + ] + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OCluster Cluster(): +{/*@bgen(jjtree) Cluster */ + OCluster jjtn000 = new OCluster(JJTCLUSTER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ Token cName; } +{/*@bgen(jjtree) Cluster */ + try { +/*@egen*/ + ( + cName = {jjtn000.clusterName = cName.image.split(":")[1];} + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OMetadataIdentifier MetadataIdentifier(): +{/*@bgen(jjtree) MetadataIdentifier */ + OMetadataIdentifier jjtn000 = new OMetadataIdentifier(JJTMETADATAIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ Token mdName; } +{/*@bgen(jjtree) MetadataIdentifier */ + try { +/*@egen*/ + ( + mdName = {jjtn000.name = mdName.image.split(":")[1];} + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OIndexIdentifier IndexIdentifier(): +{/*@bgen(jjtree) IndexIdentifier */ + OIndexIdentifier jjtn000 = new OIndexIdentifier(JJTINDEXIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + Token token; +} +{/*@bgen(jjtree) IndexIdentifier */ + try { +/*@egen*/ + ( + token = { jjtn000.type = OIndexIdentifier.Type.INDEX; } + | + token = { jjtn000.type = OIndexIdentifier.Type.VALUES; } + | + token = { jjtn000.type = OIndexIdentifier.Type.VALUESASC; } + | + token = { jjtn000.type = OIndexIdentifier.Type.VALUESDESC; } + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { + jjtn000.indexName = token.image.split(":")[1]; + return jjtn000; + }/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OWhereClause WhereClause(): +{/*@bgen(jjtree) WhereClause */ + OWhereClause jjtn000 = new OWhereClause(JJTWHERECLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) WhereClause */ + try { +/*@egen*/ + jjtn000.baseExpression = OrBlock()/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OOrBlock OrBlock(): +{/*@bgen(jjtree) OrBlock */ + OOrBlock jjtn000 = new OOrBlock(JJTORBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OAndBlock lastAnd = null; } +{/*@bgen(jjtree) OrBlock */ + try { +/*@egen*/ + ( + lastAnd = AndBlock() { jjtn000.getSubBlocks().add(lastAnd); } + ( lastAnd = AndBlock() { jjtn000.getSubBlocks().add(lastAnd); } )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OAndBlock AndBlock(): +{/*@bgen(jjtree) AndBlock */ + OAndBlock jjtn000 = new OAndBlock(JJTANDBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ONotBlock lastNot = null; } +{/*@bgen(jjtree) AndBlock */ +try { +/*@egen*/ +( + lastNot = NotBlock() { jjtn000.getSubBlocks().add(lastNot); } + ( lastNot = NotBlock() { jjtn000.getSubBlocks().add(lastNot); } )* +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return jjtn000; }/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +ONotBlock NotBlock(): +{/*@bgen(jjtree) NotBlock */ + ONotBlock jjtn000 = new ONotBlock(JJTNOTBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) NotBlock */ +try { +/*@egen*/ +( + ( + {jjtn000.negate = true;} + ( + LOOKAHEAD( ConditionBlock() ) + jjtn000.sub = ConditionBlock() + | + LOOKAHEAD( ParenthesisBlock() ) + jjtn000.sub = ParenthesisBlock() + ) + ) + | + ( + LOOKAHEAD( ConditionBlock() ) + jjtn000.sub = ConditionBlock() + | + LOOKAHEAD( ParenthesisBlock() ) + jjtn000.sub = ParenthesisBlock() + ) +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return jjtn000; }/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression ParenthesisBlock(): +{/*@bgen(jjtree) ParenthesisBlock */ + OParenthesisBlock jjtn000 = new OParenthesisBlock(JJTPARENTHESISBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ParenthesisBlock */ + try { +/*@egen*/ + ( + jjtn000.subElement = OrBlock() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression ConditionBlock(): +{/*@bgen(jjtree) ConditionBlock */ + OConditionBlock jjtn000 = new OConditionBlock(JJTCONDITIONBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/OBooleanExpression result = null;} +{/*@bgen(jjtree) ConditionBlock */ +try { +/*@egen*/ +( + LOOKAHEAD( IsNotNullCondition() ) + result = IsNotNullCondition() + | + LOOKAHEAD( IsNullCondition() ) + result = IsNullCondition() + | + LOOKAHEAD( IsNotDefinedCondition() ) + result = IsNotDefinedCondition() + | + LOOKAHEAD( IsDefinedCondition() ) + result = IsDefinedCondition() + | + LOOKAHEAD( InCondition() ) + result = InCondition() + | + LOOKAHEAD( NotInCondition() ) + result = NotInCondition() + | + LOOKAHEAD( BinaryCondition() ) + result = BinaryCondition() + | + LOOKAHEAD( BetweenCondition() ) + result = BetweenCondition() + | + LOOKAHEAD( ContainsCondition() ) + result = ContainsCondition() + | + LOOKAHEAD( ContainsValueCondition() ) + result = ContainsValueCondition() + | + LOOKAHEAD( ContainsAllCondition() ) + result = ContainsAllCondition() + | + LOOKAHEAD( ContainsTextCondition() ) + result = ContainsTextCondition() + | + LOOKAHEAD( MatchesCondition() ) + result = MatchesCondition() + | + LOOKAHEAD( IndexMatchCondition() ) + result = IndexMatchCondition() + | + LOOKAHEAD( InstanceofCondition() ) + result = InstanceofCondition() + | + { result = OBooleanExpression.TRUE;} + | + { result = OBooleanExpression.FALSE;} +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{ return result; }/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBinaryCompareOperator CompareOperator(): +{/*@bgen(jjtree) CompareOperator */ + OCompareOperator jjtn000 = new OCompareOperator(JJTCOMPAREOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OBinaryCompareOperator result;} +{/*@bgen(jjtree) CompareOperator */ +try { +/*@egen*/ +( + result = EqualsCompareOperator() + | result = LtOperator() + | result = GtOperator() + | result = NeOperator() + | result = NeqOperator() + | result = GeOperator() + | result = LeOperator() + | result = LikeOperator() + | result = ContainsKeyOperator() + | result = LuceneOperator() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return result;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +OLtOperator LtOperator(): +{/*@bgen(jjtree) LtOperator */ + OLtOperator jjtn000 = new OLtOperator(JJTLTOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) LtOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OGtOperator GtOperator(): +{/*@bgen(jjtree) GtOperator */ + OGtOperator jjtn000 = new OGtOperator(JJTGTOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) GtOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +ONeOperator NeOperator(): +{/*@bgen(jjtree) NeOperator */ + ONeOperator jjtn000 = new ONeOperator(JJTNEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) NeOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +ONeqOperator NeqOperator(): +{/*@bgen(jjtree) NeqOperator */ + ONeqOperator jjtn000 = new ONeqOperator(JJTNEQOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) NeqOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OGeOperator GeOperator(): +{/*@bgen(jjtree) GeOperator */ + OGeOperator jjtn000 = new OGeOperator(JJTGEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) GeOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OLeOperator LeOperator(): +{/*@bgen(jjtree) LeOperator */ + OLeOperator jjtn000 = new OLeOperator(JJTLEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) LeOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OLikeOperator LikeOperator(): +{/*@bgen(jjtree) LikeOperator */ + OLikeOperator jjtn000 = new OLikeOperator(JJTLIKEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) LikeOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OLuceneOperator LuceneOperator(): +{/*@bgen(jjtree) LuceneOperator */ + OLuceneOperator jjtn000 = new OLuceneOperator(JJTLUCENEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) LuceneOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OContainsKeyOperator ContainsKeyOperator(): +{/*@bgen(jjtree) ContainsKeyOperator */ + OContainsKeyOperator jjtn000 = new OContainsKeyOperator(JJTCONTAINSKEYOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsKeyOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OContainsValueOperator ContainsValueOperator(): +{/*@bgen(jjtree) ContainsValueOperator */ + OContainsValueOperator jjtn000 = new OContainsValueOperator(JJTCONTAINSVALUEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsValueOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OEqualsCompareOperator EqualsCompareOperator(): +{/*@bgen(jjtree) EqualsCompareOperator */ + OEqualsCompareOperator jjtn000 = new OEqualsCompareOperator(JJTEQUALSCOMPAREOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) EqualsCompareOperator */ +try { +/*@egen*/ +( + +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression BinaryCondition(): +{/*@bgen(jjtree) BinaryCondition */ + OBinaryCondition jjtn000 = new OBinaryCondition(JJTBINARYCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) BinaryCondition */ +try { +/*@egen*/ +( + jjtn000.left = Expression() + jjtn000.operator = CompareOperator() + jjtn000.right = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression ContainsValueCondition(): +{/*@bgen(jjtree) ContainsValueCondition */ + OContainsValueCondition jjtn000 = new OContainsValueCondition(JJTCONTAINSVALUECONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsValueCondition */ +try { +/*@egen*/ +( + jjtn000.left = Expression() + jjtn000.operator = ContainsValueOperator() + ( + LOOKAHEAD( 3 ) + jjtn000.condition = OrBlock() + | + LOOKAHEAD( Expression() ) + jjtn000.expression = Expression() + ) +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ { return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression InstanceofCondition(): +{/*@bgen(jjtree) InstanceofCondition */ + OInstanceofCondition jjtn000 = new OInstanceofCondition(JJTINSTANCEOFCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + Token token; +} +{/*@bgen(jjtree) InstanceofCondition */ + try { +/*@egen*/ + ( + jjtn000.left = Expression() ( jjtn000.right = Identifier() | token = { jjtn000.rightString = token.image; } ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression IndexMatchCondition(): +{/*@bgen(jjtree) IndexMatchCondition */ + OIndexMatchCondition jjtn000 = new OIndexMatchCondition(JJTINDEXMATCHCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + Token token; + jjtn000.leftExpressions = new ArrayList(); + OExpression lastExpression; +} +{/*@bgen(jjtree) IndexMatchCondition */ + try { +/*@egen*/ + ( + + ( + jjtn000.operator = CompareOperator() + [ + lastExpression = Expression() { jjtn000.leftExpressions.add(lastExpression); } + ( + lastExpression = Expression() { jjtn000.leftExpressions.add(lastExpression); } + )* + ] + + | + {jjtn000.between = true;} + [ + lastExpression = Expression() { jjtn000.leftExpressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtn000.leftExpressions.add(lastExpression); } + )* + ] + + [ + lastExpression = Expression() { jjtn000.rightExpressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtn000.rightExpressions.add(lastExpression); } + )* + ] + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression BetweenCondition(): +{/*@bgen(jjtree) BetweenCondition */ + OBetweenCondition jjtn000 = new OBetweenCondition(JJTBETWEENCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) BetweenCondition */ +try { +/*@egen*/ +( + jjtn000.first = Expression() + jjtn000.second = Expression() + jjtn000.third = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression IsNullCondition(): +{/*@bgen(jjtree) IsNullCondition */ + OIsNullCondition jjtn000 = new OIsNullCondition(JJTISNULLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) IsNullCondition */ + try { +/*@egen*/ + ( + jjtn000.expression = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression IsNotNullCondition(): +{/*@bgen(jjtree) IsNotNullCondition */ + OIsNotNullCondition jjtn000 = new OIsNotNullCondition(JJTISNOTNULLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) IsNotNullCondition */ +try { +/*@egen*/ +( + jjtn000.expression = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression IsDefinedCondition(): +{/*@bgen(jjtree) IsDefinedCondition */ + OIsDefinedCondition jjtn000 = new OIsDefinedCondition(JJTISDEFINEDCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) IsDefinedCondition */ +try { +/*@egen*/ +( + jjtn000.expression = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression IsNotDefinedCondition(): +{/*@bgen(jjtree) IsNotDefinedCondition */ + OIsNotDefinedCondition jjtn000 = new OIsNotDefinedCondition(JJTISNOTDEFINEDCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) IsNotDefinedCondition */ +try { +/*@egen*/ +( + jjtn000.expression = Expression() +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression ContainsCondition(): +{/*@bgen(jjtree) ContainsCondition */ + OContainsCondition jjtn000 = new OContainsCondition(JJTCONTAINSCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsCondition */ + try { +/*@egen*/ + ( + jjtn000.left = Expression() + ( + LOOKAHEAD( 3 ) + ( jjtn000.condition = OrBlock() ) + | + LOOKAHEAD( Expression() ) + jjtn000.right = Expression() + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OInOperator InOperator(): +{/*@bgen(jjtree) InOperator */ + OInOperator jjtn000 = new OInOperator(JJTINOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) InOperator */ + try { +/*@egen*/ + /*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression InCondition(): +{/*@bgen(jjtree) InCondition */ + OInCondition jjtn000 = new OInCondition(JJTINCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OExpression lastExpression; +} +{/*@bgen(jjtree) InCondition */ +try { +/*@egen*/ +( + jjtn000.left = Expression() + jjtn000.operator = InOperator() + ( + LOOKAHEAD(2) + ( jjtn000.rightStatement = SelectStatement() ) + | + LOOKAHEAD(2) + ( jjtn000.rightParam = InputParameter() ) + | + jjtn000.rightMathExpression = MathExpression() + ) +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/{return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OBooleanExpression NotInCondition(): +{/*@bgen(jjtree) NotInCondition */ + ONotInCondition jjtn000 = new ONotInCondition(JJTNOTINCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OExpression lastExpression; +} +{/*@bgen(jjtree) NotInCondition */ + try { +/*@egen*/ + ( + jjtn000.left = Expression() InOperator() + ( + LOOKAHEAD(2) + ( jjtn000.rightStatement = SelectStatement() ) + | + LOOKAHEAD(2) + ( jjtn000.rightParam = InputParameter() ) + | + jjtn000.rightMathExpression = MathExpression() + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression ContainsAllCondition(): +{/*@bgen(jjtree) ContainsAllCondition */ + OContainsAllCondition jjtn000 = new OContainsAllCondition(JJTCONTAINSALLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsAllCondition */ + try { +/*@egen*/ + ( + jjtn000.left = Expression() + + ( + LOOKAHEAD( 3 ) + ( jjtn000.rightBlock = OrBlock() ) + | + LOOKAHEAD( Expression() ) + jjtn000.right = Expression() + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression ContainsTextCondition(): +{/*@bgen(jjtree) ContainsTextCondition */ + OContainsTextCondition jjtn000 = new OContainsTextCondition(JJTCONTAINSTEXTCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) ContainsTextCondition */ + try { +/*@egen*/ + ( + jjtn000.left = Expression() jjtn000.right = Expression() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OBooleanExpression MatchesCondition(): +{/*@bgen(jjtree) MatchesCondition */ + OMatchesCondition jjtn000 = new OMatchesCondition(JJTMATCHESCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/Token token;} +{/*@bgen(jjtree) MatchesCondition */ + try { +/*@egen*/ + ( + jjtn000.expression = Expression() token = {jjtn000.right = token.image;} + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OOrderBy OrderBy(): +{/*@bgen(jjtree) OrderBy */ + OOrderBy jjtn000 = new OOrderBy(JJTORDERBY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + jjtn000.items = new java.util.ArrayList(); + OOrderByItem lastItem; + OIdentifier lastIdentifier; + ORid lastRid; + Token lastToken; +} +{/*@bgen(jjtree) OrderBy */ +try { +/*@egen*/ +( + + ( + { + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + } + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + ( + "," + ( + { + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + } + ( + lastIdentifier = Identifier() { lastItem.alias = lastIdentifier.toString(); } + | + lastItem.rid = Rid() + | + lastToken = { lastItem.recordAttr = lastToken.image; } + ) + ) + [ { lastItem.type = OOrderByItem.DESC; }| { lastItem.type = OOrderByItem.ASC; }] + )* +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + +OGroupBy GroupBy(): +{/*@bgen(jjtree) GroupBy */ + OGroupBy jjtn000 = new OGroupBy(JJTGROUPBY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OIdentifier lastIdentifier; } +{/*@bgen(jjtree) GroupBy */ +try { +/*@egen*/ +( + lastIdentifier = Identifier() { jjtn000.items.add(lastIdentifier); } + ( + "," + lastIdentifier = Identifier() { jjtn000.items.add(lastIdentifier); } + )* +)/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return jjtn000;}/*@bgen(jjtree)*/ +} catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } +} +/*@egen*/ +} + + +java.lang.Integer Limit(): +{/*@bgen(jjtree) Limit */ + OLimit jjtn000 = new OLimit(JJTLIMIT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInteger value = null; } +{/*@bgen(jjtree) Limit */ + try { +/*@egen*/ + ( + value = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return value.getValue(); }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +java.lang.Integer Skip(): +{/*@bgen(jjtree) Skip */ + OSkip jjtn000 = new OSkip(JJTSKIP); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInteger value = null;} +{/*@bgen(jjtree) Skip */ + try { +/*@egen*/ + ( + value = Integer() + | + value = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ {return value.getValue();}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +java.lang.Integer Timeout(): +{/*@bgen(jjtree) Timeout */ + OTimeout jjtn000 = new OTimeout(JJTTIMEOUT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInteger val; } +{/*@bgen(jjtree) Timeout */ + try { +/*@egen*/ + ( + val = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return val.getValue(); }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +java.lang.Integer Wait(): +{/*@bgen(jjtree) Wait */ + OWait jjtn000 = new OWait(JJTWAIT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInteger val; } +{/*@bgen(jjtree) Wait */ + try { +/*@egen*/ + ( + val = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return val.getValue(); }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + +java.lang.Integer Retry(): +{/*@bgen(jjtree) Retry */ + ORetry jjtn000 = new ORetry(JJTRETRY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OInteger val; } +{/*@bgen(jjtree) Retry */ + try { +/*@egen*/ + ( + val = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return val.getValue(); }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + + + + + + +OCollection Collection(): +{/*@bgen(jjtree) Collection */ + OCollection jjtn000 = new OCollection(JJTCOLLECTION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OExpression lastExpression; +} +{/*@bgen(jjtree) Collection */ + try { +/*@egen*/ + ( + + + [ + lastExpression = Expression() { jjtn000.expressions.add(lastExpression); } + ( + + lastExpression = Expression() { jjtn000.expressions.add(lastExpression); } + )* + ] + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + + +OFetchPlan FetchPlan(): +{/*@bgen(jjtree) FetchPlan */ + OFetchPlan jjtn000 = new OFetchPlan(JJTFETCHPLAN); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OFetchPlanItem lastItem; } +{/*@bgen(jjtree) FetchPlan */ + try { +/*@egen*/ + ( + lastItem = FetchPlanItem() { jjtn000.items.add(lastItem); } + ( lastItem = FetchPlanItem() { jjtn000.items.add(lastItem); } )* + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OFetchPlanItem FetchPlanItem(): +{/*@bgen(jjtree) FetchPlanItem */ + OFetchPlanItem jjtn000 = new OFetchPlanItem(JJTFETCHPLANITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ OIdentifier lastIdentifier; + boolean lastStarred = false; +} +{/*@bgen(jjtree) FetchPlanItem */ + try { +/*@egen*/ + ( + ( + { jjtn000.star = true; } + | + [ jjtn000.leftDepth = Integer() ] + lastIdentifier = Identifier() { lastStarred = false; } [ { lastStarred = true; }] + { + String field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtn000.fieldChain.add(field); + } + ( + lastIdentifier = Identifier() { lastStarred = false; } [ { lastStarred = true; } ] + { + field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtn000.fieldChain.add(field); + } + )* + ) + jjtn000.rightDepth = Integer() + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + + +OTraverseProjectionItem TraverseProjectionItem(): +{/*@bgen(jjtree) TraverseProjectionItem */ + OTraverseProjectionItem jjtn000 = new OTraverseProjectionItem(JJTTRAVERSEPROJECTIONITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/} +{/*@bgen(jjtree) TraverseProjectionItem */ + try { +/*@egen*/ + ( + { jjtn000.star = true; } + | + ( + jjtn000.base = BaseIdentifier() + [ LOOKAHEAD( Modifier() ) jjtn000.modifier = Modifier() ] + ) + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + +OArray Array(): +{/*@bgen(jjtree) Array */ + OArray jjtn000 = new OArray(JJTARRAY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + jjtn000.expressions = new java.util.ArrayList(); + OExpression currentExpr; +} +{/*@bgen(jjtree) Array */ + try { +/*@egen*/ + ( + [ currentExpr = Expression() { jjtn000.expressions.add(currentExpr); } + ( currentExpr = Expression() { jjtn000.expressions.add(currentExpr); } )* ] + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + { return jjtn000; }/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ + +} + + +OJson Json(): +{/*@bgen(jjtree) Json */ + OJson jjtn000 = new OJson(JJTJSON); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); +/*@egen*/ + OJsonItem lastItem; + Token token; +} +{/*@bgen(jjtree) Json */ + try { +/*@egen*/ + ( + + [ + { lastItem = new OJsonItem(); } + ( + lastItem.leftIdentifier = Identifier() + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + ) + + lastItem.right = Expression() { jjtn000.items.add(lastItem); } + ( + + { lastItem = new OJsonItem(); } + ( + lastItem.leftIdentifier = Identifier() + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + | + token = { lastItem.leftString = token.image.substring(1, token.image.length() - 1); } + ) + + lastItem.right = Expression() { jjtn000.items.add(lastItem); } + )* + ] + + )/*@bgen(jjtree)*/ + { + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + } +/*@egen*/ + {return jjtn000;}/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } +/*@egen*/ +} + + + + diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSql.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSql.java new file mode 100644 index 00000000000..ac7edc0e810 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSql.java @@ -0,0 +1,27645 @@ +/* Generated By:JJTree&JavaCC: Do not edit this line. OrientSql.java */ +package com.orientechnologies.orient.core.sql.parser; + +import java.io.InputStream; +import java.util.List; +import java.util.ArrayList; +import com.orientechnologies.orient.core.sql.OCommandSQLParsingException; +import com.orientechnologies.orient.core.exception.OQueryParsingException; + +/** Orient Database Sql grammar. */ +public class OrientSql/*@bgen(jjtree)*/implements OrientSqlTreeConstants, OrientSqlConstants {/*@bgen(jjtree)*/ + protected JJTOrientSqlState jjtree = new JJTOrientSqlState(); + private int inputParamCount = 0; + + + public OrientSql(InputStream stream) { + this(new JavaCharStream(stream)); + } + + final public ORid Rid() throws ParseException { + /*@bgen(jjtree) Rid */ + ORid jjtn000 = new ORid(JJTRID); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + if (jj_2_1(4)) { + jj_consume_token(246); + jjtn000.cluster = Integer(); + jj_consume_token(COLON); + jjtn000.position = Integer(); + } else if (jj_2_2(3)) { + jjtn000.cluster = Integer(); + jj_consume_token(COLON); + jjtn000.position = Integer(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + +/** Root productions. */ + final public OStatement parse() throws ParseException { + /*@bgen(jjtree) parse */ + Oparse jjtn000 = new Oparse(JJTPARSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement result; + try { + result = Statement(); + jj_consume_token(0); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public List parseScript() throws ParseException { + /*@bgen(jjtree) parseScript */ + OparseScript jjtn000 = new OparseScript(JJTPARSESCRIPT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));List result = new ArrayList(); + OStatement last; + try { + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + case TRAVERSE: + case MATCH: + case INSERT: + case CREATE: + case DELETE: + case UPDATE: + case RETURN: + case LET: + case PROFILE: + case TRUNCATE: + case FIND: + case ALTER: + case DROP: + case REBUILD: + case OPTIMIZE: + case EXPLAIN: + case GRANT: + case REVOKE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case SLEEP: + case CONSOLE: + case HA: + case MOVE: + case SEMICOLON: + ; + break; + default: + jj_la1[0] = jj_gen; + break label_1; + } + if (jj_2_3(2147483647)) { + last = StatementSemicolon(); + result.add(last); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IF: + last = IfStatement(); + result.add(last); + break; + case SEMICOLON: + jj_consume_token(SEMICOLON); + break; + default: + jj_la1[1] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jj_consume_token(0); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OIdentifier Identifier() throws ParseException { + /*@bgen(jjtree) Identifier */ + OIdentifier jjtn000 = new OIdentifier(JJTIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token quotedToken = null; + Token token = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IDENTIFIER: + token = jj_consume_token(IDENTIFIER); + break; + case IN: + token = jj_consume_token(IN); + break; + case SET: + token = jj_consume_token(SET); + break; + case PUT: + token = jj_consume_token(PUT); + break; + case ADD: + token = jj_consume_token(ADD); + break; + case REMOVE: + token = jj_consume_token(REMOVE); + break; + case MERGE: + token = jj_consume_token(MERGE); + break; + case CONTENT: + token = jj_consume_token(CONTENT); + break; + case ORDER: + token = jj_consume_token(ORDER); + break; + case KEY: + token = jj_consume_token(KEY); + break; + case OFFSET: + token = jj_consume_token(OFFSET); + break; + case GROUP: + token = jj_consume_token(GROUP); + break; + case VALUE: + token = jj_consume_token(VALUE); + break; + case VALUES: + token = jj_consume_token(VALUES); + break; + case RECORD: + token = jj_consume_token(RECORD); + break; + case TO: + token = jj_consume_token(TO); + break; + case LUCENE: + token = jj_consume_token(LUCENE); + break; + case CLASS: + token = jj_consume_token(CLASS); + break; + case CLASSES: + token = jj_consume_token(CLASSES); + break; + case MINDEPTH: + token = jj_consume_token(MINDEPTH); + break; + case NEAR: + token = jj_consume_token(NEAR); + break; + case WITHIN: + token = jj_consume_token(WITHIN); + break; + case EXCEPTION: + token = jj_consume_token(EXCEPTION); + break; + case PROFILE: + token = jj_consume_token(PROFILE); + break; + case STORAGE: + token = jj_consume_token(STORAGE); + break; + case ON: + token = jj_consume_token(ON); + break; + case OFF: + token = jj_consume_token(OFF); + break; + case TRUNCATE: + token = jj_consume_token(TRUNCATE); + break; + case FIND: + token = jj_consume_token(FIND); + break; + case REFERENCES: + token = jj_consume_token(REFERENCES); + break; + case EXTENDS: + token = jj_consume_token(EXTENDS); + break; + case CLUSTERS: + token = jj_consume_token(CLUSTERS); + break; + case ABSTRACT: + token = jj_consume_token(ABSTRACT); + break; + case ALTER: + token = jj_consume_token(ALTER); + break; + case NAME: + token = jj_consume_token(NAME); + break; + case SHORTNAME: + token = jj_consume_token(SHORTNAME); + break; + case SUPERCLASS: + token = jj_consume_token(SUPERCLASS); + break; + case SUPERCLASSES: + token = jj_consume_token(SUPERCLASSES); + break; + case OVERSIZE: + token = jj_consume_token(OVERSIZE); + break; + case STRICTMODE: + token = jj_consume_token(STRICTMODE); + break; + case ADDCLUSTER: + token = jj_consume_token(ADDCLUSTER); + break; + case REMOVECLUSTER: + token = jj_consume_token(REMOVECLUSTER); + break; + case CUSTOM: + token = jj_consume_token(CUSTOM); + break; + case CLUSTERSELECTION: + token = jj_consume_token(CLUSTERSELECTION); + break; + case DESCRIPTION: + token = jj_consume_token(DESCRIPTION); + break; + case ENCRYPTION: + token = jj_consume_token(ENCRYPTION); + break; + case DROP: + token = jj_consume_token(DROP); + break; + case PROPERTY: + token = jj_consume_token(PROPERTY); + break; + case FORCE: + token = jj_consume_token(FORCE); + break; + case METADATA: + token = jj_consume_token(METADATA); + break; + case COLLATE: + token = jj_consume_token(COLLATE); + break; + case INDEX: + token = jj_consume_token(INDEX); + break; + case ENGINE: + token = jj_consume_token(ENGINE); + break; + case REBUILD: + token = jj_consume_token(REBUILD); + break; + case ID: + token = jj_consume_token(ID); + break; + case DATABASE: + token = jj_consume_token(DATABASE); + break; + case OPTIMIZE: + token = jj_consume_token(OPTIMIZE); + break; + case LINK: + token = jj_consume_token(LINK); + break; + case TYPE: + token = jj_consume_token(TYPE); + break; + case INVERSE: + token = jj_consume_token(INVERSE); + break; + case EXPLAIN: + token = jj_consume_token(EXPLAIN); + break; + case GRANT: + token = jj_consume_token(GRANT); + break; + case REVOKE: + token = jj_consume_token(REVOKE); + break; + case READ: + token = jj_consume_token(READ); + break; + case EXECUTE: + token = jj_consume_token(EXECUTE); + break; + case ALL: + token = jj_consume_token(ALL); + break; + case NONE: + token = jj_consume_token(NONE); + break; + case FUNCTION: + token = jj_consume_token(FUNCTION); + break; + case PARAMETERS: + token = jj_consume_token(PARAMETERS); + break; + case IDEMPOTENT: + token = jj_consume_token(IDEMPOTENT); + break; + case LANGUAGE: + token = jj_consume_token(LANGUAGE); + break; + case BEGIN: + token = jj_consume_token(BEGIN); + break; + case COMMIT: + token = jj_consume_token(COMMIT); + break; + case ROLLBACK: + token = jj_consume_token(ROLLBACK); + break; + case IF: + token = jj_consume_token(IF); + break; + case ISOLATION: + token = jj_consume_token(ISOLATION); + break; + case SLEEP: + token = jj_consume_token(SLEEP); + break; + case CONSOLE: + token = jj_consume_token(CONSOLE); + break; + case BLOB: + token = jj_consume_token(BLOB); + break; + case SHARED: + token = jj_consume_token(SHARED); + break; + case DEFAULT_: + token = jj_consume_token(DEFAULT_); + break; + case SEQUENCE: + token = jj_consume_token(SEQUENCE); + break; + case CACHE: + token = jj_consume_token(CACHE); + break; + case START: + token = jj_consume_token(START); + break; + case OPTIONAL: + token = jj_consume_token(OPTIONAL); + break; + case COUNT: + token = jj_consume_token(COUNT); + break; + case HA: + token = jj_consume_token(HA); + break; + case STATUS: + token = jj_consume_token(STATUS); + break; + case SERVER: + token = jj_consume_token(SERVER); + break; + case SYNC: + token = jj_consume_token(SYNC); + break; + case EXISTS: + token = jj_consume_token(EXISTS); + break; + case RID: + token = jj_consume_token(RID); + break; + case RIDS: + token = jj_consume_token(RIDS); + break; + case MOVE: + token = jj_consume_token(MOVE); + break; + case QUOTED_IDENTIFIER: + quotedToken = jj_consume_token(QUOTED_IDENTIFIER); + break; + default: + jj_la1[2] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(token!=null){ + jjtn000.value = token.image; + }else{ + jjtn000.quoted = true; + jjtn000.value = quotedToken.image; + jjtn000.value = jjtn000.value.substring(1, jjtn000.value.length() - 1); + /*try{ + jjtThis.value = java.net.URLEncoder.encode(jjtThis.value, null); + }catch(Exception e){ + + }*/ + } + + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OInteger Integer() throws ParseException { + /*@bgen(jjtree) Integer */ + OInteger jjtn000 = new OInteger(JJTINTEGER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));int sign = 1; + Token tokenVal; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + sign = -1; + break; + default: + jj_la1[3] = jj_gen; + ; + } + tokenVal = jj_consume_token(INTEGER_LITERAL); + jjtn000.value = sign * Long.parseLong(tokenVal.image); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFloatingPoint FloatingPoint() throws ParseException { + /*@bgen(jjtree) FloatingPoint */ + OFloatingPoint jjtn000 = new OFloatingPoint(JJTFLOATINGPOINT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));String stringValue; + Token tokenVal; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + jjtn000.sign = -1; + break; + default: + jj_la1[4] = jj_gen; + ; + } + tokenVal = jj_consume_token(FLOATING_POINT_LITERAL); + jjtn000.stringValue = tokenVal.image; + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONumber Number() throws ParseException { + /*@bgen(jjtree) Number */ + ONumber jjtn000 = new ONumber(JJTNUMBER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ONumber result; + try { + if (jj_2_4(2147483647)) { + result = Integer(); + } else if (jj_2_5(2147483647)) { + result = FloatingPoint(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OStatement Statement() throws ParseException { + /*@bgen(jjtree) Statement */ + OStatement jjtn000 = new OStatement(JJTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement result = null; + try { + result = StatementInternal(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SEMICOLON: + jj_consume_token(SEMICOLON); + break; + default: + jj_la1[5] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OStatement StatementSemicolon() throws ParseException { + /*@bgen(jjtree) StatementSemicolon */ + OStatementSemicolon jjtn000 = new OStatementSemicolon(JJTSTATEMENTSEMICOLON); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement result = null; + try { + result = StatementInternal(); + jj_consume_token(SEMICOLON); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OStatement StatementInternal() throws ParseException { + /*@bgen(jjtree) StatementInternal */ + OStatementInternal jjtn000 = new OStatementInternal(JJTSTATEMENTINTERNAL); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement result = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + case TRAVERSE: + case MATCH: + case INSERT: + case CREATE: + case DELETE: + case UPDATE: + case RETURN: + case PROFILE: + case TRUNCATE: + case FIND: + case ALTER: + case DROP: + case REBUILD: + case OPTIMIZE: + case GRANT: + case REVOKE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case SLEEP: + case CONSOLE: + case HA: + case MOVE: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + case TRAVERSE: + case MATCH: + case FIND: + result = QueryStatement(); + break; + default: + jj_la1[6] = jj_gen; + if (jj_2_6(2)) { + result = DeleteStatement(); + } else if (jj_2_7(2)) { + result = DeleteVertexStatement(); + } else if (jj_2_8(2)) { + result = DeleteEdgeStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INSERT: + result = InsertStatement(); + break; + default: + jj_la1[7] = jj_gen; + if (jj_2_9(2)) { + result = CreateClassStatement(); + } else if (jj_2_10(2)) { + result = CreatePropertyStatement(); + } else if (jj_2_11(2)) { + result = CreateIndexStatement(); + } else if (jj_2_12(2)) { + result = CreateClusterStatement(); + } else if (jj_2_13(2)) { + result = CreateLinkStatement(); + } else if (jj_2_14(2)) { + result = CreateFunctionStatement(); + } else if (jj_2_15(2)) { + result = CreateSequenceStatement(); + } else if (jj_2_16(2147483647)) { + result = CreateVertexStatementNoTarget(); + } else if (jj_2_17(2147483647)) { + result = CreateVertexStatement(); + } else if (jj_2_18(2147483647)) { + result = CreateVertexStatementEmpty(); + } else if (jj_2_19(2147483647)) { + result = CreateVertexStatementEmptyNoTarget(); + } else if (jj_2_20(2147483647)) { + result = MoveVertexStatement(); + } else if (jj_2_21(2147483647)) { + result = CreateEdgeStatement(); + } else if (jj_2_22(2147483647)) { + result = UpdateEdgeStatement(); + } else if (jj_2_23(2147483647)) { + result = UpdateStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PROFILE: + result = ProfileStorageStatement(); + break; + default: + jj_la1[8] = jj_gen; + if (jj_2_24(2147483647)) { + result = TruncateClassStatement(); + } else if (jj_2_25(2147483647)) { + result = TruncateClusterStatement(); + } else if (jj_2_26(2147483647)) { + result = TruncateRecordStatement(); + } else if (jj_2_27(2)) { + result = AlterSequenceStatement(); + } else if (jj_2_28(2147483647)) { + result = AlterClassStatement(); + } else if (jj_2_29(2)) { + result = DropSequenceStatement(); + } else if (jj_2_30(2147483647)) { + result = DropClassStatement(); + } else if (jj_2_31(2147483647)) { + result = AlterPropertyStatement(); + } else if (jj_2_32(2147483647)) { + result = DropPropertyStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case REBUILD: + result = RebuildIndexStatement(); + break; + default: + jj_la1[9] = jj_gen; + if (jj_2_33(2)) { + result = DropIndexStatement(); + } else if (jj_2_34(2147483647)) { + result = AlterClusterStatement(); + } else if (jj_2_35(2)) { + result = DropClusterStatement(); + } else if (jj_2_36(2)) { + result = AlterDatabaseStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case OPTIMIZE: + result = OptimizeDatabaseStatement(); + break; + case GRANT: + result = GrantStatement(); + break; + case REVOKE: + result = RevokeStatement(); + break; + case BEGIN: + result = BeginStatement(); + break; + case COMMIT: + result = CommitStatement(); + break; + case ROLLBACK: + result = RollbackStatement(); + break; + case RETURN: + result = ReturnStatement(); + break; + case SLEEP: + result = SleepStatement(); + break; + case CONSOLE: + result = ConsoleStatement(); + break; + case IF: + result = IfStatement(); + break; + default: + jj_la1[10] = jj_gen; + if (jj_2_37(2)) { + result = HaStatusStatement(); + } else if (jj_2_38(2)) { + result = HaRemoveServerStatement(); + } else if (jj_2_39(3)) { + result = HaSyncDatabaseStatement(); + } else if (jj_2_40(3)) { + result = HaSyncClusterStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + } + } + } + } + } + } + } + } + break; + case EXPLAIN: + result = ExplainStatement(); + break; + case LET: + result = LetStatement(); + break; + default: + jj_la1[11] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OStatement QueryStatement() throws ParseException { + /*@bgen(jjtree) QueryStatement */ + OQueryStatement jjtn000 = new OQueryStatement(JJTQUERYSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement result; + try { + if (jj_2_41(2147483647)) { + result = SelectStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + result = SelectWithoutTargetStatement(); + break; + case TRAVERSE: + result = TraverseStatement(); + break; + case MATCH: + result = MatchStatement(); + break; + default: + jj_la1[12] = jj_gen; + if (jj_2_42(2147483647)) { + result = FindReferencesStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OSelectWithoutTargetStatement SelectWithoutTargetStatement() throws ParseException { + /*@bgen(jjtree) SelectWithoutTargetStatement */ + OSelectWithoutTargetStatement jjtn000 = new OSelectWithoutTargetStatement(JJTSELECTWITHOUTTARGETSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(SELECT); + jjtn000.projection = Projection(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LET: + jjtn000.letClause = LetClause(); + break; + default: + jj_la1[13] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNWIND: + jjtn000.unwind = Unwind(); + break; + default: + jj_la1[14] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + case SKIP2: + case OFFSET: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SKIP2: + case OFFSET: + jjtn000.skip = Skip(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[15] = jj_gen; + ; + } + break; + case LIMIT: + jjtn000.limit = Limit(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SKIP2: + case OFFSET: + jjtn000.skip = Skip(); + break; + default: + jj_la1[16] = jj_gen; + ; + } + break; + default: + jj_la1[17] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[18] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FETCHPLAN: + jjtn000.fetchPlan = FetchPlan(); + break; + default: + jj_la1[19] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TIMEOUT: + jjtn000.timeout = Timeout(); + break; + default: + jj_la1[20] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LOCK: + jj_consume_token(LOCK); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RECORD: + jj_consume_token(RECORD); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK; + break; + case NONE: + jj_consume_token(NONE); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE; + break; + case SHARED: + jj_consume_token(SHARED); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK; + break; + case DEFAULT_: + jj_consume_token(DEFAULT_); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT; + break; + default: + jj_la1[21] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[22] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PARALLEL: + jj_consume_token(PARALLEL); + jjtn000.parallel = true; + break; + default: + jj_la1[23] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NOCACHE: + jj_consume_token(NOCACHE); + jjtn000.noCache = true; + break; + default: + jj_la1[24] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.validate(); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OSelectStatement SelectStatement() throws ParseException { + /*@bgen(jjtree) SelectStatement */ + OSelectStatement jjtn000 = new OSelectStatement(JJTSELECTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(SELECT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + jjtn000.projection = Projection(); + break; + default: + jj_la1[25] = jj_gen; + ; + } + jj_consume_token(FROM); + jjtn000.target = FromClause(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LET: + jjtn000.letClause = LetClause(); + break; + default: + jj_la1[26] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[27] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case GROUP: + jjtn000.groupBy = GroupBy(); + break; + default: + jj_la1[28] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ORDER: + jjtn000.orderBy = OrderBy(); + break; + default: + jj_la1[29] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNWIND: + jjtn000.unwind = Unwind(); + break; + default: + jj_la1[30] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + case SKIP2: + case OFFSET: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SKIP2: + case OFFSET: + jjtn000.skip = Skip(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[31] = jj_gen; + ; + } + break; + case LIMIT: + jjtn000.limit = Limit(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SKIP2: + case OFFSET: + jjtn000.skip = Skip(); + break; + default: + jj_la1[32] = jj_gen; + ; + } + break; + default: + jj_la1[33] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[34] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FETCHPLAN: + jjtn000.fetchPlan = FetchPlan(); + break; + default: + jj_la1[35] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TIMEOUT: + jjtn000.timeout = Timeout(); + break; + default: + jj_la1[36] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LOCK: + jj_consume_token(LOCK); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RECORD: + jj_consume_token(RECORD); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK; + break; + case NONE: + jj_consume_token(NONE); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE; + break; + case SHARED: + jj_consume_token(SHARED); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK; + break; + case DEFAULT_: + jj_consume_token(DEFAULT_); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT; + break; + default: + jj_la1[37] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[38] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PARALLEL: + jj_consume_token(PARALLEL); + jjtn000.parallel = true; + break; + default: + jj_la1[39] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NOCACHE: + jj_consume_token(NOCACHE); + jjtn000.noCache = true; + break; + default: + jj_la1[40] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.validate(); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTraverseStatement TraverseStatement() throws ParseException { + /*@bgen(jjtree) TraverseStatement */ + OTraverseStatement jjtn000 = new OTraverseStatement(JJTTRAVERSESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OTraverseProjectionItem lastProjection; + try { + jj_consume_token(TRAVERSE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case LBRACKET: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastProjection = TraverseProjectionItem(); + jjtn000.projections.add(lastProjection); + label_2: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[41] = jj_gen; + break label_2; + } + jj_consume_token(COMMA); + lastProjection = TraverseProjectionItem(); + jjtn000.projections.add(lastProjection); + } + break; + default: + jj_la1[42] = jj_gen; + ; + } + jj_consume_token(FROM); + jjtn000.target = FromClause(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MAXDEPTH: + jj_consume_token(MAXDEPTH); + jjtn000.maxDepth = Integer(); + break; + default: + jj_la1[43] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHILE: + jj_consume_token(WHILE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[44] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[45] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STRATEGY: + jj_consume_token(STRATEGY); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DEPTH_FIRST: + jj_consume_token(DEPTH_FIRST); + jjtn000.strategy = OTraverseStatement.Strategy.DEPTH_FIRST; + break; + case BREADTH_FIRST: + jj_consume_token(BREADTH_FIRST); + jjtn000.strategy = OTraverseStatement.Strategy.BREADTH_FIRST; + break; + default: + jj_la1[46] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[47] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchStatement MatchStatement() throws ParseException { + /*@bgen(jjtree) MatchStatement */ + OMatchStatement jjtn000 = new OMatchStatement(JJTMATCHSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMatchExpression lastMatchExpr = null; + OExpression lastReturn = null; + OIdentifier lastReturnAlias = null; + try { + jj_consume_token(MATCH); + lastMatchExpr = MatchExpression(); + jjtn000.matchExpressions.add(lastMatchExpr); + label_3: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[48] = jj_gen; + break label_3; + } + jj_consume_token(COMMA); + lastMatchExpr = MatchExpression(); + jjtn000.matchExpressions.add(lastMatchExpr); + } + jj_consume_token(RETURN); + lastReturn = Expression(); + lastReturnAlias = null; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case AS: + jj_consume_token(AS); + lastReturnAlias = Identifier(); + break; + default: + jj_la1[49] = jj_gen; + ; + } + jjtn000.returnAliases.add(lastReturnAlias); + jjtn000.returnItems.add(lastReturn); + label_4: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[50] = jj_gen; + break label_4; + } + jj_consume_token(COMMA); + lastReturn = Expression(); + lastReturnAlias = null; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case AS: + jj_consume_token(AS); + lastReturnAlias = Identifier(); + break; + default: + jj_la1[51] = jj_gen; + ; + } + jjtn000.returnAliases.add(lastReturnAlias); + jjtn000.returnItems.add(lastReturn); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[52] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteStatement DeleteStatement() throws ParseException { + /*@bgen(jjtree) DeleteStatement */ + ODeleteStatement jjtn000 = new ODeleteStatement(JJTDELETESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DELETE); + jj_consume_token(FROM); + jjtn000.fromClause = FromClause(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + jj_consume_token(BEFORE); + jjtn000.returnBefore = true; + break; + default: + jj_la1[53] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[54] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[55] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[56] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteVertexStatement DeleteVertexStatement() throws ParseException { + /*@bgen(jjtree) DeleteVertexStatement */ + ODeleteVertexStatement jjtn000 = new ODeleteVertexStatement(JJTDELETEVERTEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DELETE); + jj_consume_token(VERTEX); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FROM: + jj_consume_token(FROM); + jjtn000.from = true; + break; + default: + jj_la1[57] = jj_gen; + ; + } + jjtn000.fromClause = FromClause(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + jj_consume_token(BEFORE); + jjtn000.returnBefore = true; + break; + default: + jj_la1[58] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[59] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[60] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[61] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMoveVertexStatement MoveVertexStatement() throws ParseException { + /*@bgen(jjtree) MoveVertexStatement */ + OMoveVertexStatement jjtn000 = new OMoveVertexStatement(JJTMOVEVERTEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastSetExpr; + try { + jj_consume_token(MOVE); + jj_consume_token(VERTEX); + jjtn000.source = FromItem(); + jj_consume_token(TO); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER_IDENTIFIER: + case CLUSTER_NUMBER_IDENTIFIER: + jjtn000.targetCluster = Cluster(); + break; + case CLASS: + jj_consume_token(CLASS); + jj_consume_token(COLON); + jjtn000.targetClass = Identifier(); + break; + default: + jj_la1[62] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case INCREMENT: + jjtn000.updateOperations = UpdateOperations(); + break; + default: + jj_la1[63] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[64] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeStatement */ + ODeleteEdgeStatement jjtn000 = new ODeleteEdgeStatement(JJTDELETEEDGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ODeleteEdgeStatement result; + try { + if (jj_2_43(2147483647)) { + result = DeleteEdgeByRidStatement(); + } else if (jj_2_44(2147483647)) { + result = DeleteEdgeFromToStatement(); + } else if (jj_2_45(2147483647)) { + result = DeleteEdgeVToStatement(); + } else if (jj_2_46(2147483647)) { + result = DeleteEdgeToStatement(); + } else if (jj_2_47(2147483647)) { + result = DeleteEdgeWhereStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeByRidStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeByRidStatement */ + ODeleteEdgeByRidStatement jjtn000 = new ODeleteEdgeByRidStatement(JJTDELETEEDGEBYRIDSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(DELETE); + jj_consume_token(EDGE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.rid = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.rids = new ArrayList(); + jjtn000.rids.add(lastRid); + label_5: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[65] = jj_gen; + break label_5; + } + jj_consume_token(COMMA); + lastRid = Rid(); + jjtn000.rids.add(lastRid); + } + break; + default: + jj_la1[66] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[67] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[68] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeFromToStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeFromToStatement */ + ODeleteEdgeFromToStatement jjtn000 = new ODeleteEdgeFromToStatement(JJTDELETEEDGEFROMTOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(DELETE); + jj_consume_token(EDGE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.className = Identifier(); + break; + default: + jj_la1[69] = jj_gen; + ; + } + jj_consume_token(FROM); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.leftRid = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.leftRids=new ArrayList(); + jjtn000.leftRids.add(lastRid); + label_6: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[70] = jj_gen; + break label_6; + } + jj_consume_token(COMMA); + lastRid = Rid(); + jjtn000.leftRids.add(lastRid); + } + break; + default: + jj_la1[71] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + case LPAREN: + jj_consume_token(LPAREN); + if (jj_2_48(2147483647)) { + jjtn000.leftStatement = SelectStatement(); + } else if (jj_2_49(2147483647)) { + jjtn000.leftStatement = SelectWithoutTargetStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RPAREN); + break; + case HOOK: + case COLON: + jjtn000.leftParam = InputParameter(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.leftIdentifier = Identifier(); + break; + default: + jj_la1[72] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + jj_consume_token(TO); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.rightRid = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + label_7: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[73] = jj_gen; + break label_7; + } + jj_consume_token(COMMA); + lastRid = Rid(); + jjtn000.rightRids.add(lastRid); + } + break; + default: + jj_la1[74] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + case LPAREN: + jj_consume_token(LPAREN); + if (jj_2_50(2147483647)) { + jjtn000.rightStatement = SelectStatement(); + } else if (jj_2_51(2147483647)) { + jjtn000.rightStatement = SelectWithoutTargetStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RPAREN); + break; + case HOOK: + case COLON: + jjtn000.rightParam = InputParameter(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.rightIdentifier = Identifier(); + break; + default: + jj_la1[75] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[76] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[77] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[78] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[79] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeToStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeToStatement */ + ODeleteEdgeToStatement jjtn000 = new ODeleteEdgeToStatement(JJTDELETEEDGETOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(DELETE); + jj_consume_token(EDGE); + jjtn000.className = Identifier(); + jj_consume_token(TO); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.rightRid = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + label_8: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[80] = jj_gen; + break label_8; + } + jj_consume_token(COMMA); + lastRid = Rid(); + jjtn000.rightRids.add(lastRid); + } + break; + default: + jj_la1[81] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + case LPAREN: + jj_consume_token(LPAREN); + if (jj_2_52(2147483647)) { + jjtn000.rightStatement = SelectStatement(); + } else if (jj_2_53(2147483647)) { + jjtn000.rightStatement = SelectWithoutTargetStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RPAREN); + break; + case HOOK: + case COLON: + jjtn000.rightParam = InputParameter(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.rightIdentifier = Identifier(); + break; + default: + jj_la1[82] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[83] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[84] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[85] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeVToStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeVToStatement */ + ODeleteEdgeVToStatement jjtn000 = new ODeleteEdgeVToStatement(JJTDELETEEDGEVTOSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(DELETE); + jj_consume_token(EDGE); + jj_consume_token(TO); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.rightRid = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.rightRids=new ArrayList(); + jjtn000.rightRids.add(lastRid); + label_9: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[86] = jj_gen; + break label_9; + } + jj_consume_token(COMMA); + lastRid = Rid(); + jjtn000.rightRids.add(lastRid); + } + break; + default: + jj_la1[87] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + case LPAREN: + jj_consume_token(LPAREN); + if (jj_2_54(2147483647)) { + jjtn000.rightStatement = SelectStatement(); + } else if (jj_2_55(2147483647)) { + jjtn000.rightStatement = SelectWithoutTargetStatement(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RPAREN); + break; + case HOOK: + case COLON: + jjtn000.rightParam = InputParameter(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.rightIdentifier = Identifier(); + break; + default: + jj_la1[88] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[89] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[90] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[91] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODeleteEdgeStatement DeleteEdgeWhereStatement() throws ParseException { + /*@bgen(jjtree) DeleteEdgeWhereStatement */ + ODeleteEdgeWhereStatement jjtn000 = new ODeleteEdgeWhereStatement(JJTDELETEEDGEWHERESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(DELETE); + jj_consume_token(EDGE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.className = Identifier(); + break; + default: + jj_la1[92] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[93] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[94] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[95] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateEdgeStatement UpdateEdgeStatement() throws ParseException { + /*@bgen(jjtree) UpdateEdgeStatement */ + OUpdateEdgeStatement jjtn000 = new OUpdateEdgeStatement(JJTUPDATEEDGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OUpdateOperations lastOperations; + ORid lastRid; + try { + jj_consume_token(UPDATE); + jj_consume_token(EDGE); + jjtn000.target = FromClause(); + label_10: + while (true) { + lastOperations = UpdateOperations(); + jjtn000.operations.add(lastOperations); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case INCREMENT: + ; + break; + default: + jj_la1[96] = jj_gen; + break label_10; + } + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UPSERT: + jj_consume_token(UPSERT); + jjtn000.upsert = true; + break; + default: + jj_la1[97] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BEFORE: + jj_consume_token(BEFORE); + jjtn000.returnBefore = true; + break; + case AFTER: + jj_consume_token(AFTER); + jjtn000.returnAfter = true; + break; + default: + jj_la1[98] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + jjtn000.returnProjection = Projection(); + break; + default: + jj_la1[99] = jj_gen; + ; + } + break; + default: + jj_la1[100] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[101] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LOCK: + jj_consume_token(LOCK); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RECORD: + jj_consume_token(RECORD); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK; + break; + case NONE: + jj_consume_token(NONE); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE; + break; + case SHARED: + jj_consume_token(SHARED); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK; + break; + case DEFAULT_: + jj_consume_token(DEFAULT_); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT; + break; + default: + jj_la1[102] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[103] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[104] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TIMEOUT: + jjtn000.timeout = Timeout(); + break; + default: + jj_la1[105] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateStatement UpdateStatement() throws ParseException { + /*@bgen(jjtree) UpdateStatement */ + OUpdateStatement jjtn000 = new OUpdateStatement(JJTUPDATESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OUpdateOperations lastOperations; + ORid lastRid; + try { + jj_consume_token(UPDATE); + jjtn000.target = FromClause(); + label_11: + while (true) { + lastOperations = UpdateOperations(); + jjtn000.operations.add(lastOperations); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case INCREMENT: + ; + break; + default: + jj_la1[106] = jj_gen; + break label_11; + } + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UPSERT: + jj_consume_token(UPSERT); + jjtn000.upsert = true; + break; + default: + jj_la1[107] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BEFORE: + jj_consume_token(BEFORE); + jjtn000.returnBefore = true; + break; + case AFTER: + jj_consume_token(AFTER); + jjtn000.returnAfter = true; + break; + case COUNT: + jj_consume_token(COUNT); + jjtn000.returnCount = true; + break; + default: + jj_la1[108] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + jjtn000.returnProjection = Projection(); + break; + default: + jj_la1[109] = jj_gen; + ; + } + break; + default: + jj_la1[110] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LET: + jjtn000.let = LetClause(); + break; + default: + jj_la1[111] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + jj_consume_token(WHERE); + jjtn000.whereClause = WhereClause(); + break; + default: + jj_la1[112] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LOCK: + jj_consume_token(LOCK); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RECORD: + jj_consume_token(RECORD); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK; + break; + case NONE: + jj_consume_token(NONE); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.NONE; + break; + case SHARED: + jj_consume_token(SHARED); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.SHARED_LOCK; + break; + case DEFAULT_: + jj_consume_token(DEFAULT_); + jjtn000.lockRecord = com.orientechnologies.orient.core.storage.OStorage.LOCKING_STRATEGY.DEFAULT; + break; + default: + jj_la1[113] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[114] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LIMIT: + jjtn000.limit = Limit(); + break; + default: + jj_la1[115] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TIMEOUT: + jjtn000.timeout = Timeout(); + break; + default: + jj_la1[116] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateOperations UpdateOperations() throws ParseException { + /*@bgen(jjtree) UpdateOperations */ + OUpdateOperations jjtn000 = new OUpdateOperations(JJTUPDATEOPERATIONS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OUpdateItem lastItem; + OUpdatePutItem lastPutItem; + OUpdateIncrementItem lastIncrementItem; + OUpdateRemoveItem lastRemoveItem; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SET: + jj_consume_token(SET); + jjtn000.type = OUpdateOperations.TYPE_SET; + lastItem = UpdateItem(); + jjtn000.updateItems.add(lastItem); + label_12: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[117] = jj_gen; + break label_12; + } + jj_consume_token(COMMA); + lastItem = UpdateItem(); + jjtn000.updateItems.add(lastItem); + } + break; + case PUT: + jj_consume_token(PUT); + jjtn000.type = OUpdateOperations.TYPE_PUT; + lastPutItem = UpdatePutItem(); + jjtn000.updatePutItems.add(lastPutItem); + label_13: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[118] = jj_gen; + break label_13; + } + jj_consume_token(COMMA); + lastPutItem = UpdatePutItem(); + jjtn000.updatePutItems.add(lastPutItem); + } + break; + case MERGE: + case CONTENT: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MERGE: + jj_consume_token(MERGE); + jjtn000.type = OUpdateOperations.TYPE_MERGE; + break; + case CONTENT: + jj_consume_token(CONTENT); + jjtn000.type = OUpdateOperations.TYPE_CONTENT; + break; + default: + jj_la1[119] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.json = Json(); + break; + case ADD: + case INCREMENT: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INCREMENT: + jj_consume_token(INCREMENT); + jjtn000.type = OUpdateOperations.TYPE_INCREMENT; + break; + case ADD: + jj_consume_token(ADD); + jjtn000.type = OUpdateOperations.TYPE_ADD; + break; + default: + jj_la1[120] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + lastIncrementItem = UpdateIncrementItem(); + jjtn000.updateIncrementItems.add(lastIncrementItem); + label_14: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[121] = jj_gen; + break label_14; + } + jj_consume_token(COMMA); + lastIncrementItem = UpdateIncrementItem(); + jjtn000.updateIncrementItems.add(lastIncrementItem); + } + break; + case REMOVE: + jj_consume_token(REMOVE); + jjtn000.type = OUpdateOperations.TYPE_REMOVE; + lastRemoveItem = UpdateRemoveItem(); + jjtn000.updateRemoveItems.add(lastRemoveItem); + label_15: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[122] = jj_gen; + break label_15; + } + jj_consume_token(COMMA); + lastRemoveItem = UpdateRemoveItem(); + jjtn000.updateRemoveItems.add(lastRemoveItem); + } + break; + default: + jj_la1[123] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateItem UpdateItem() throws ParseException { + /*@bgen(jjtree) UpdateItem */ + OUpdateItem jjtn000 = new OUpdateItem(JJTUPDATEITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + jjtn000.leftModifier = Modifier(); + break; + default: + jj_la1[124] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EQ: + jj_consume_token(EQ); + jjtn000.operator = OUpdateItem.OPERATOR_EQ; + break; + case PLUSASSIGN: + jj_consume_token(PLUSASSIGN); + jjtn000.operator = OUpdateItem.OPERATOR_PLUSASSIGN; + break; + case MINUSASSIGN: + jj_consume_token(MINUSASSIGN); + jjtn000.operator = OUpdateItem.OPERATOR_MINUSASSIGN; + break; + case STARASSIGN: + jj_consume_token(STARASSIGN); + jjtn000.operator = OUpdateItem.OPERATOR_STARASSIGN; + break; + case SLASHASSIGN: + jj_consume_token(SLASHASSIGN); + jjtn000.operator = OUpdateItem.OPERATOR_SLASHASSIGN; + break; + default: + jj_la1[125] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.right = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateIncrementItem UpdateIncrementItem() throws ParseException { + /*@bgen(jjtree) UpdateIncrementItem */ + OUpdateIncrementItem jjtn000 = new OUpdateIncrementItem(JJTUPDATEINCREMENTITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + jjtn000.leftModifier = Modifier(); + break; + default: + jj_la1[126] = jj_gen; + ; + } + jj_consume_token(EQ); + jjtn000.right = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateRemoveItem UpdateRemoveItem() throws ParseException { + /*@bgen(jjtree) UpdateRemoveItem */ + OUpdateRemoveItem jjtn000 = new OUpdateRemoveItem(JJTUPDATEREMOVEITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + jjtn000.leftModifier = Modifier(); + break; + default: + jj_la1[127] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EQ: + jj_consume_token(EQ); + jjtn000.right = Expression(); + break; + default: + jj_la1[128] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdatePutItem UpdatePutItem() throws ParseException { + /*@bgen(jjtree) UpdatePutItem */ + OUpdatePutItem jjtn000 = new OUpdatePutItem(JJTUPDATEPUTITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Identifier(); + jj_consume_token(EQ); + jjtn000.key = Expression(); + jj_consume_token(COMMA); + jjtn000.value = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUpdateAddItem UpdateAddItem() throws ParseException { + /*@bgen(jjtree) UpdateAddItem */ + OUpdateAddItem jjtn000 = new OUpdateAddItem(JJTUPDATEADDITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Identifier(); + jj_consume_token(EQ); + jjtn000.right = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OInsertStatement InsertStatement() throws ParseException { + /*@bgen(jjtree) InsertStatement */ + OInsertStatement jjtn000 = new OInsertStatement(JJTINSERTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(INSERT); + jj_consume_token(INTO); + if (jj_2_56(2147483647)) { + jjtn000.targetIndex = IndexIdentifier(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.targetClass = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + jjtn000.targetClusterName = Identifier(); + break; + default: + jj_la1[129] = jj_gen; + ; + } + break; + case CLUSTER_IDENTIFIER: + case CLUSTER_NUMBER_IDENTIFIER: + jjtn000.targetCluster = Cluster(); + break; + default: + jj_la1[130] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + if (jj_2_57(2147483647)) { + jjtn000.insertBody = InsertBody(); + } else { + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + jjtn000.returnStatement = Projection(); + break; + default: + jj_la1[131] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + case FROM: + case LPAREN: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FROM: + jj_consume_token(FROM); + jjtn000.selectWithFrom = true; + break; + default: + jj_la1[132] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + if (jj_2_58(2147483647)) { + jjtn000.selectStatement = SelectStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + jjtn000.selectStatement = SelectWithoutTargetStatement(); + break; + default: + jj_la1[133] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + break; + default: + jj_la1[135] = jj_gen; + if (jj_2_60(2)) { + jj_consume_token(LPAREN); + if (jj_2_59(2147483647)) { + jjtn000.selectStatement = SelectStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + jjtn000.selectStatement = SelectWithoutTargetStatement(); + break; + default: + jj_la1[134] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtn000.selectInParentheses = true; + jj_consume_token(RPAREN); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + break; + default: + jj_la1[136] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[137] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OInsertBody InsertBody() throws ParseException { + /*@bgen(jjtree) InsertBody */ + OInsertBody jjtn000 = new OInsertBody(JJTINSERTBODY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + OExpression lastExpression; + List lastExpressionList; + try { + if (jj_2_61(3)) { + jj_consume_token(LPAREN); + lastIdentifier = Identifier(); + jjtn000.identifierList = new ArrayList(); + jjtn000.identifierList.add(lastIdentifier); + label_16: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[138] = jj_gen; + break label_16; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.identifierList.add(lastIdentifier); + } + jj_consume_token(RPAREN); + jj_consume_token(VALUES); + jj_consume_token(LPAREN); + jjtn000.valueExpressions = new ArrayList>(); + lastExpressionList = new ArrayList(); + jjtn000.valueExpressions.add(lastExpressionList); + lastExpression = Expression(); + lastExpressionList.add(lastExpression); + label_17: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[139] = jj_gen; + break label_17; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + lastExpressionList.add(lastExpression); + } + jj_consume_token(RPAREN); + label_18: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[140] = jj_gen; + break label_18; + } + jj_consume_token(COMMA); + jj_consume_token(LPAREN); + lastExpressionList = new ArrayList(); + jjtn000.valueExpressions.add(lastExpressionList); + lastExpression = Expression(); + lastExpressionList.add(lastExpression); + label_19: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[141] = jj_gen; + break label_19; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + lastExpressionList.add(lastExpression); + } + jj_consume_token(RPAREN); + } + } else if (jj_2_62(3)) { + jj_consume_token(SET); + jjtn000.setExpressions = new ArrayList(); + OInsertSetExpression lastSetExpr = new OInsertSetExpression(); + jjtn000.setExpressions.add(lastSetExpr); + lastSetExpr.left = Identifier(); + jj_consume_token(EQ); + lastSetExpr.right = Expression(); + label_20: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[142] = jj_gen; + break label_20; + } + jj_consume_token(COMMA); + lastSetExpr = new OInsertSetExpression(); + jjtn000.setExpressions.add(lastSetExpr); + lastSetExpr.left = Identifier(); + jj_consume_token(EQ); + lastSetExpr.right = Expression(); + } + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CONTENT: + jj_consume_token(CONTENT); + jjtn000.content = Json(); + break; + default: + jj_la1[143] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateVertexStatementEmptyNoTarget CreateVertexStatementEmptyNoTarget() throws ParseException { + /*@bgen(jjtree) CreateVertexStatementEmptyNoTarget */ + OCreateVertexStatementEmptyNoTarget jjtn000 = new OCreateVertexStatementEmptyNoTarget(JJTCREATEVERTEXSTATEMENTEMPTYNOTARGET); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + jj_consume_token(VERTEX); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateVertexStatementEmpty CreateVertexStatementEmpty() throws ParseException { + /*@bgen(jjtree) CreateVertexStatementEmpty */ + OCreateVertexStatementEmpty jjtn000 = new OCreateVertexStatementEmpty(JJTCREATEVERTEXSTATEMENTEMPTY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + jj_consume_token(VERTEX); + jjtn000.targetClass = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + jjtn000.targetClusterName = Identifier(); + break; + default: + jj_la1[144] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateVertexStatement CreateVertexStatement() throws ParseException { + /*@bgen(jjtree) CreateVertexStatement */ + OCreateVertexStatement jjtn000 = new OCreateVertexStatement(JJTCREATEVERTEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + jj_consume_token(VERTEX); + if (jj_2_63(2147483647)) { + jjtn000.targetClass = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + jjtn000.targetClusterName = Identifier(); + break; + default: + jj_la1[145] = jj_gen; + ; + } + } else if (jj_2_64(2147483647)) { + jjtn000.targetCluster = Cluster(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + jjtn000.returnStatement = Projection(); + break; + default: + jj_la1[146] = jj_gen; + ; + } + if (jj_2_65(2147483647)) { + jjtn000.insertBody = InsertBody(); + } else { + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateVertexStatementNoTarget CreateVertexStatementNoTarget() throws ParseException { + /*@bgen(jjtree) CreateVertexStatementNoTarget */ + OCreateVertexStatementNoTarget jjtn000 = new OCreateVertexStatementNoTarget(JJTCREATEVERTEXSTATEMENTNOTARGET); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + jj_consume_token(VERTEX); + jjtn000.insertBody = InsertBody(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateEdgeStatement CreateEdgeStatement() throws ParseException { + /*@bgen(jjtree) CreateEdgeStatement */ + OCreateEdgeStatement jjtn000 = new OCreateEdgeStatement(JJTCREATEEDGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRid; + try { + jj_consume_token(CREATE); + jj_consume_token(EDGE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.targetClass = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + jjtn000.targetClusterName = Identifier(); + break; + default: + jj_la1[147] = jj_gen; + ; + } + break; + default: + jj_la1[148] = jj_gen; + ; + } + jj_consume_token(FROM); + jjtn000.leftExpression = Expression(); + jj_consume_token(TO); + jjtn000.rightExpression = Expression(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SET: + case CONTENT: + case LPAREN: + jjtn000.body = InsertBody(); + break; + default: + jj_la1[149] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETRY: + jjtn000.retry = Retry(); + break; + default: + jj_la1[150] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WAIT: + jjtn000.wait = Wait(); + break; + default: + jj_la1[151] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BATCH: + jjtn000.batch = Batch(); + break; + default: + jj_la1[152] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OInputParameter InputParameter() throws ParseException { + /*@bgen(jjtree) InputParameter */ + OInputParameter jjtn000 = new OInputParameter(JJTINPUTPARAMETER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OInputParameter result; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case HOOK: + result = PositionalParameter(); + break; + case COLON: + result = NamedParameter(); + break; + default: + jj_la1[153] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OPositionalParameter PositionalParameter() throws ParseException { + /*@bgen(jjtree) PositionalParameter */ + OPositionalParameter jjtn000 = new OPositionalParameter(JJTPOSITIONALPARAMETER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(HOOK); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.paramNumber = inputParamCount; + inputParamCount++; + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONamedParameter NamedParameter() throws ParseException { + /*@bgen(jjtree) NamedParameter */ +ONamedParameter jjtn000 = new ONamedParameter(JJTNAMEDPARAMETER); +boolean jjtc000 = true; +jjtree.openNodeScope(jjtn000); +jjtn000.jjtSetFirstToken(getToken(1));OIdentifier identifierParam; +Token token; + try { + jj_consume_token(COLON); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + identifierParam = Identifier(); + jjtn000.paramName = identifierParam.toString(); + break; + case SKIP2: + token = jj_consume_token(SKIP2); + jjtn000.paramName = token.image; + break; + case LIMIT: + token = jj_consume_token(LIMIT); + jjtn000.paramName = token.image; + break; + case FROM: + token = jj_consume_token(FROM); + jjtn000.paramName = token.image; + break; + default: + jj_la1[154] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.paramNumber = inputParamCount; + inputParamCount++; + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OProjection Projection() throws ParseException { + /*@bgen(jjtree) Projection */ + OProjection jjtn000 = new OProjection(JJTPROJECTION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));java.util.List items = new java.util.ArrayList(); + OProjectionItem lastItem = null; + try { + lastItem = ProjectionItem(); + items.add(lastItem); + label_21: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[155] = jj_gen; + break label_21; + } + jj_consume_token(COMMA); + lastItem = ProjectionItem(); + items.add(lastItem); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.items = items; + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OProjectionItem ProjectionItem() throws ParseException { + /*@bgen(jjtree) ProjectionItem */ + OProjectionItem jjtn000 = new OProjectionItem(JJTPROJECTIONITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.expression = Expression(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case AS: + jj_consume_token(AS); + jjtn000.alias = Alias(); + break; + default: + jj_la1[156] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OArraySelector ArraySelector() throws ParseException { + /*@bgen(jjtree) ArraySelector */ + OArraySelector jjtn000 = new OArraySelector(JJTARRAYSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + if (jj_2_66(2147483647)) { + jjtn000.rid = Rid(); + } else if (jj_2_67(2147483647)) { + jjtn000.inputParam = InputParameter(); + } else if (jj_2_68(2147483647)) { + jjtn000.expression = Expression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OArrayNumberSelector ArrayNumberSelector() throws ParseException { + /*@bgen(jjtree) ArrayNumberSelector */ + OArrayNumberSelector jjtn000 = new OArrayNumberSelector(JJTARRAYNUMBERSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token tokenVal; + try { + if (jj_2_69(2147483647)) { + jjtn000.inputValue = InputParameter(); + } else if (jj_2_70(2147483647)) { + tokenVal = jj_consume_token(INTEGER_LITERAL); + jjtn000.integer = Integer.parseInt(tokenVal.image); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OArraySingleValuesSelector ArraySingleValuesSelector() throws ParseException { + /*@bgen(jjtree) ArraySingleValuesSelector */ + OArraySingleValuesSelector jjtn000 = new OArraySingleValuesSelector(JJTARRAYSINGLEVALUESSELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OArraySelector lastSelector; + try { + lastSelector = ArraySelector(); + jjtn000.items.add(lastSelector); + label_22: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[157] = jj_gen; + break label_22; + } + jj_consume_token(COMMA); + lastSelector = ArraySelector(); + jjtn000.items.add(lastSelector); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OArrayRangeSelector ArrayRangeSelector() throws ParseException { + /*@bgen(jjtree) ArrayRangeSelector */ + OArrayRangeSelector jjtn000 = new OArrayRangeSelector(JJTARRAYRANGESELECTOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + jjtn000.fromSelector = ArrayNumberSelector(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + case RANGE: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + break; + case RANGE: + jj_consume_token(RANGE); + jjtn000.newRange = true; + break; + default: + jj_la1[158] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[159] = jj_gen; + ; + } + jjtn000.toSelector = ArrayNumberSelector(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OIdentifier Alias() throws ParseException { + /*@bgen(jjtree) Alias */ + OAlias jjtn000 = new OAlias(JJTALIAS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier identifier; + try { + identifier = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return identifier;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ORecordAttribute RecordAttribute() throws ParseException { + /*@bgen(jjtree) RecordAttribute */ + ORecordAttribute jjtn000 = new ORecordAttribute(JJTRECORDATTRIBUTE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + token = jj_consume_token(RECORD_ATTRIBUTE); + jjtn000.name = token.image; + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFunctionCall FunctionCall() throws ParseException { + /*@bgen(jjtree) FunctionCall */ + OFunctionCall jjtn000 = new OFunctionCall(JJTFUNCTIONCALL); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression = null; + try { + jjtn000.name = Identifier(); + jj_consume_token(LPAREN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.params.add(lastExpression); + label_23: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[160] = jj_gen; + break label_23; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.params.add(lastExpression); + } + break; + default: + jj_la1[161] = jj_gen; + ; + } + jj_consume_token(RPAREN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMethodCall MethodCall() throws ParseException { + /*@bgen(jjtree) MethodCall */ + OMethodCall jjtn000 = new OMethodCall(JJTMETHODCALL); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression; + try { + jj_consume_token(DOT); + jjtn000.methodName = Identifier(); + jj_consume_token(LPAREN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.params.add(lastExpression); + label_24: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[162] = jj_gen; + break label_24; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.params.add(lastExpression); + } + break; + default: + jj_la1[163] = jj_gen; + ; + } + jj_consume_token(RPAREN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLevelZeroIdentifier LevelZeroIdentifier() throws ParseException { + /*@bgen(jjtree) LevelZeroIdentifier */ + OLevelZeroIdentifier jjtn000 = new OLevelZeroIdentifier(JJTLEVELZEROIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + if (jj_2_71(2147483647)) { + jjtn000.functionCall = FunctionCall(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case THIS: + jj_consume_token(THIS); + jjtn000.self = true; + break; + default: + jj_la1[164] = jj_gen; + if (jj_2_72(2147483647)) { + jjtn000.collection = Collection(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OSuffixIdentifier SuffixIdentifier() throws ParseException { + /*@bgen(jjtree) SuffixIdentifier */ + OSuffixIdentifier jjtn000 = new OSuffixIdentifier(JJTSUFFIXIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + if (jj_2_73(2147483647)) { + jjtn000.identifier = Identifier(); + } else if (jj_2_74(2147483647)) { + jjtn000.recordAttribute = RecordAttribute(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + jjtn000.star = true; + break; + default: + jj_la1[165] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBaseIdentifier BaseIdentifier() throws ParseException { + /*@bgen(jjtree) BaseIdentifier */ + OBaseIdentifier jjtn000 = new OBaseIdentifier(JJTBASEIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + if (jj_2_75(2147483647)) { + jjtn000.levelZero = LevelZeroIdentifier(); + } else if (jj_2_76(2147483647)) { + jjtn000.suffix = SuffixIdentifier(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OModifier Modifier() throws ParseException { + /*@bgen(jjtree) Modifier */ + OModifier jjtn000 = new OModifier(JJTMODIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + jj_consume_token(LBRACKET); + jjtn000.squareBrackets = true; + if (jj_2_77(2147483647)) { + jjtn000.arrayRange = ArrayRangeSelector(); + } else if (jj_2_78(2147483647)) { + jjtn000.condition = OrBlock(); + } else if (jj_2_79(2147483647)) { + jjtn000.arraySingleValues = ArraySingleValuesSelector(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[166] = jj_gen; + if (jj_2_80(2147483647)) { + jjtn000.methodCall = MethodCall(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + jj_consume_token(DOT); + jjtn000.suffix = SuffixIdentifier(); + break; + default: + jj_la1[167] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } + if (jj_2_81(2147483647)) { + jjtn000.next = Modifier(); + } else { + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OExpression Expression() throws ParseException { + /*@bgen(jjtree) Expression */ + OExpression jjtn000 = new OExpression(JJTEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NULL: + jj_consume_token(NULL); + jjtn000.value = null; + break; + default: + jj_la1[168] = jj_gen; + if (jj_2_82(2147483647)) { + jjtn000.value = Rid(); + } else if (jj_2_83(2147483647)) { + jjtn000.value = MathExpression(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.value = Json(); + break; + case TRUE: + jj_consume_token(TRUE); + jjtn000.value = true; + break; + case FALSE: + jj_consume_token(FALSE); + jjtn000.value = false; + break; + default: + jj_la1[169] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMathExpression MathExpression() throws ParseException { + /*@bgen(jjtree) MathExpression */ + OMathExpression jjtn000 = new OMathExpression(JJTMATHEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMathExpression sub; + jjtn000.setChildExpressions(new java.util.ArrayList()); + try { + sub = MultExpression(); + jjtn000.getChildExpressions().add(sub); + label_25: + while (true) { + if (jj_2_84(2)) { + ; + } else { + break label_25; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PLUS: + jj_consume_token(PLUS); + jjtn000.operators.add( OMathExpression.Operator.PLUS); + break; + case MINUS: + jj_consume_token(MINUS); + jjtn000.operators.add(OMathExpression.Operator.MINUS); + break; + default: + jj_la1[170] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + sub = MultExpression(); + jjtn000.getChildExpressions().add(sub); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(jjtn000.getChildExpressions().size() != 1){ + {if (true) return jjtn000;} + }else{ + {if (true) return jjtn000.getChildExpressions().get(0);} + } + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMathExpression MultExpression() throws ParseException { + /*@bgen(jjtree) MultExpression */ + OMultExpression jjtn000 = new OMultExpression(JJTMULTEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMathExpression sub; + jjtn000.setChildExpressions(new java.util.ArrayList()); + try { + sub = FirstLevelExpression(); + jjtn000.getChildExpressions().add(sub); + label_26: + while (true) { + if (jj_2_85(2)) { + ; + } else { + break label_26; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + jjtn000.operators.add( OMathExpression.Operator.STAR); + break; + case SLASH: + jj_consume_token(SLASH); + jjtn000.operators.add( OMathExpression.Operator.SLASH); + break; + case REM: + jj_consume_token(REM); + jjtn000.operators.add( OMathExpression.Operator.REM); + break; + default: + jj_la1[171] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + sub = FirstLevelExpression(); + jjtn000.getChildExpressions().add(sub); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(jjtn000.getChildExpressions().size() != 1){ + {if (true) return jjtn000;} + }else{ + {if (true) return jjtn000.getChildExpressions().get(0);} + } + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMathExpression FirstLevelExpression() throws ParseException { + /*@bgen(jjtree) FirstLevelExpression */ + OFirstLevelExpression jjtn000 = new OFirstLevelExpression(JJTFIRSTLEVELEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMathExpression expr; + try { + if (jj_2_86(2147483647)) { + expr = ParenthesisExpression(); + } else if (jj_2_87(2147483647)) { + expr = BaseExpression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return expr;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMathExpression ParenthesisExpression() throws ParseException { + /*@bgen(jjtree) ParenthesisExpression */ + OParenthesisExpression jjtn000 = new OParenthesisExpression(JJTPARENTHESISEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LPAREN); + if (jj_2_88(2)) { + jjtn000.statement = QueryStatement(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + jjtn000.expression = Expression(); + break; + case INSERT: + jjtn000.statement = InsertStatement(); + break; + default: + jj_la1[172] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jj_consume_token(RPAREN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBaseExpression BaseExpression() throws ParseException { + /*@bgen(jjtree) BaseExpression */ + OBaseExpression jjtn000 = new OBaseExpression(JJTBASEEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case MINUS: + jjtn000.number = Number(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case LBRACKET: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifier = BaseIdentifier(); + if (jj_2_89(2147483647)) { + jjtn000.modifier = Modifier(); + } else { + ; + } + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + if (jj_2_90(2147483647)) { + jjtn000.modifier = Modifier(); + } else { + ; + } + break; + case CHARACTER_LITERAL: + case STRING_LITERAL: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STRING_LITERAL: + token = jj_consume_token(STRING_LITERAL); + jjtn000.string = token.image; + break; + case CHARACTER_LITERAL: + token = jj_consume_token(CHARACTER_LITERAL); + jjtn000.string = token.image; + break; + default: + jj_la1[173] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + if (jj_2_91(2147483647)) { + jjtn000.modifier = Modifier(); + } else { + ; + } + break; + default: + jj_la1[174] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFromClause FromClause() throws ParseException { + /*@bgen(jjtree) FromClause */ + OFromClause jjtn000 = new OFromClause(JJTFROMCLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.item = FromItem(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLetClause LetClause() throws ParseException { + /*@bgen(jjtree) LetClause */ + OLetClause jjtn000 = new OLetClause(JJTLETCLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OLetItem lastItem; + try { + jj_consume_token(LET); + lastItem = LetItem(); + jjtn000.items.add(lastItem); + label_27: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[175] = jj_gen; + break label_27; + } + jj_consume_token(COMMA); + lastItem = LetItem(); + jjtn000.items.add(lastItem); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLetItem LetItem() throws ParseException { + /*@bgen(jjtree) LetItem */ + OLetItem jjtn000 = new OLetItem(JJTLETITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.varName = Identifier(); + jj_consume_token(EQ); + if (jj_2_92(2147483647)) { + jjtn000.expression = Expression(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LPAREN: + jj_consume_token(LPAREN); + jjtn000.query = QueryStatement(); + jj_consume_token(RPAREN); + break; + default: + jj_la1[176] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + if(jjtn000.varName.getStringValue().equalsIgnoreCase("$root")|| + jjtn000.varName.getStringValue().equalsIgnoreCase("root")|| + jjtn000.varName.getStringValue().equalsIgnoreCase("$parent")|| + jjtn000.varName.getStringValue().equalsIgnoreCase("parent")|| + jjtn000.varName.getStringValue().equalsIgnoreCase("$current")|| + jjtn000.varName.getStringValue().equalsIgnoreCase("current")){ + {if (true) throw new OCommandSQLParsingException("invalid LET statement: "+jjtn000.varName+" is a reserved keyword");} + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFromItem FromItem() throws ParseException { + /*@bgen(jjtree) FromItem */ + OFromItem jjtn000 = new OFromItem(JJTFROMITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));jjtn000.rids = new java.util.ArrayList(); + ORid lastRid; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRid = Rid(); + jjtn000.rids.add(lastRid); + break; + case CLUSTER_IDENTIFIER: + case CLUSTER_NUMBER_IDENTIFIER: + /*( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + ( + lastRid = Rid() { jjtThis.rids.add(lastRid); } + )* + ) + |*/ + jjtn000.cluster = Cluster(); + break; + case CLUSTER: + jjtn000.clusterList = ClusterList(); + break; + default: + jj_la1[177] = jj_gen; + if (jj_2_94(2147483647)) { + jjtn000.index = IndexIdentifier(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case METADATA_IDENTIFIER: + jjtn000.metadata = MetadataIdentifier(); + break; + case LPAREN: + jj_consume_token(LPAREN); + jjtn000.statement = QueryStatement(); + jj_consume_token(RPAREN); + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case LBRACKET: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifier = BaseIdentifier(); + if (jj_2_93(2147483647)) { + jjtn000.modifier = Modifier(); + } else { + ; + } + break; + default: + jj_la1[178] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCluster Cluster() throws ParseException { + /*@bgen(jjtree) Cluster */ + OCluster jjtn000 = new OCluster(JJTCLUSTER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token cName; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER_IDENTIFIER: + cName = jj_consume_token(CLUSTER_IDENTIFIER); + jjtn000.clusterName = cName.image.split(":")[1]; + break; + case CLUSTER_NUMBER_IDENTIFIER: + cName = jj_consume_token(CLUSTER_NUMBER_IDENTIFIER); + jjtn000.clusterNumber = Integer.parseInt(cName.image.split(":")[1]); + break; + default: + jj_la1[179] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OClusterList ClusterList() throws ParseException { + /*@bgen(jjtree) ClusterList */ + OClusterList jjtn000 = new OClusterList(JJTCLUSTERLIST); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + try { + jj_consume_token(CLUSTER); + jj_consume_token(COLON); + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + jjtn000.clusters.add(lastIdentifier); + label_28: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[180] = jj_gen; + break label_28; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.clusters.add(lastIdentifier); + } + break; + default: + jj_la1[181] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMetadataIdentifier MetadataIdentifier() throws ParseException { + /*@bgen(jjtree) MetadataIdentifier */ + OMetadataIdentifier jjtn000 = new OMetadataIdentifier(JJTMETADATAIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token mdName; + try { + mdName = jj_consume_token(METADATA_IDENTIFIER); + jjtn000.name = mdName.image.split(":")[1]; + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OIndexName IndexName() throws ParseException { + /*@bgen(jjtree) IndexName */ + OIndexName jjtn000 = new OIndexName(JJTINDEXNAME); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));StringBuilder builder = new StringBuilder(); + Token token; + OIdentifier lastIdentifier; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 247: + jj_consume_token(247); + builder.append("__@recordmap@___"); + break; + default: + jj_la1[182] = jj_gen; + ; + } + lastIdentifier = Identifier(); + builder.append(lastIdentifier.getValue()); + label_29: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + case MINUS: + ; + break; + default: + jj_la1[183] = jj_gen; + break label_29; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + jj_consume_token(DOT); + builder.append("."); + break; + case MINUS: + jj_consume_token(MINUS); + builder.append("-"); + break; + default: + jj_la1[184] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + lastIdentifier = Identifier(); + builder.append(lastIdentifier.getValue()); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + jjtn000.value = builder.toString(); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OIndexIdentifier IndexIdentifier() throws ParseException { + /*@bgen(jjtree) IndexIdentifier */ + OIndexIdentifier jjtn000 = new OIndexIdentifier(JJTINDEXIDENTIFIER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INDEX_COLON: + jj_consume_token(INDEX_COLON); + jjtn000.indexName = IndexName(); + jjtn000.type = OIndexIdentifier.Type.INDEX; + break; + case INDEXVALUES_IDENTIFIER: + case INDEXVALUESASC_IDENTIFIER: + case INDEXVALUESDESC_IDENTIFIER: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INDEXVALUES_IDENTIFIER: + token = jj_consume_token(INDEXVALUES_IDENTIFIER); + jjtn000.type = OIndexIdentifier.Type.VALUES; + break; + case INDEXVALUESASC_IDENTIFIER: + token = jj_consume_token(INDEXVALUESASC_IDENTIFIER); + jjtn000.type = OIndexIdentifier.Type.VALUESASC; + break; + case INDEXVALUESDESC_IDENTIFIER: + token = jj_consume_token(INDEXVALUESDESC_IDENTIFIER); + jjtn000.type = OIndexIdentifier.Type.VALUESDESC; + break; + default: + jj_la1[185] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.indexNameString = token.image.split(":")[1]; + break; + default: + jj_la1[186] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OWhereClause WhereClause() throws ParseException { + /*@bgen(jjtree) WhereClause */ + OWhereClause jjtn000 = new OWhereClause(JJTWHERECLAUSE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.baseExpression = OrBlock(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OOrBlock OrBlock() throws ParseException { + /*@bgen(jjtree) OrBlock */ + OOrBlock jjtn000 = new OOrBlock(JJTORBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OAndBlock lastAnd = null; + try { + lastAnd = AndBlock(); + jjtn000.getSubBlocks().add(lastAnd); + label_30: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case OR: + ; + break; + default: + jj_la1[187] = jj_gen; + break label_30; + } + jj_consume_token(OR); + lastAnd = AndBlock(); + jjtn000.getSubBlocks().add(lastAnd); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAndBlock AndBlock() throws ParseException { + /*@bgen(jjtree) AndBlock */ + OAndBlock jjtn000 = new OAndBlock(JJTANDBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ONotBlock lastNot = null; + try { + lastNot = NotBlock(); + jjtn000.getSubBlocks().add(lastNot); + label_31: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case AND: + ; + break; + default: + jj_la1[188] = jj_gen; + break label_31; + } + jj_consume_token(AND); + lastNot = NotBlock(); + jjtn000.getSubBlocks().add(lastNot); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONotBlock NotBlock() throws ParseException { + /*@bgen(jjtree) NotBlock */ + ONotBlock jjtn000 = new ONotBlock(JJTNOTBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NOT: + jj_consume_token(NOT); + jjtn000.negate = true; + if (jj_2_95(2147483647)) { + jjtn000.sub = ConditionBlock(); + } else if (jj_2_96(2147483647)) { + jjtn000.sub = ParenthesisBlock(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + if (jj_2_97(2147483647)) { + jjtn000.sub = ConditionBlock(); + } else if (jj_2_98(2147483647)) { + jjtn000.sub = ParenthesisBlock(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[189] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ParenthesisBlock() throws ParseException { + /*@bgen(jjtree) ParenthesisBlock */ + OParenthesisBlock jjtn000 = new OParenthesisBlock(JJTPARENTHESISBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LPAREN); + jjtn000.subElement = OrBlock(); + jj_consume_token(RPAREN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ConditionBlock() throws ParseException { + /*@bgen(jjtree) ConditionBlock */ + OConditionBlock jjtn000 = new OConditionBlock(JJTCONDITIONBLOCK); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OBooleanExpression result = null; + try { + if (jj_2_99(2147483647)) { + result = IsNotNullCondition(); + } else if (jj_2_100(2147483647)) { + result = IsNullCondition(); + } else if (jj_2_101(2147483647)) { + result = IsNotDefinedCondition(); + } else if (jj_2_102(2147483647)) { + result = IsDefinedCondition(); + } else if (jj_2_103(2147483647)) { + result = InCondition(); + } else if (jj_2_104(2147483647)) { + result = NotInCondition(); + } else if (jj_2_105(2147483647)) { + result = BinaryCondition(); + } else if (jj_2_106(2147483647)) { + result = BetweenCondition(); + } else if (jj_2_107(2147483647)) { + result = ContainsCondition(); + } else if (jj_2_108(2147483647)) { + result = ContainsValueCondition(); + } else if (jj_2_109(2147483647)) { + result = ContainsAllCondition(); + } else if (jj_2_110(2147483647)) { + result = ContainsTextCondition(); + } else if (jj_2_111(2147483647)) { + result = MatchesCondition(); + } else if (jj_2_112(2147483647)) { + result = IndexMatchCondition(); + } else if (jj_2_113(2147483647)) { + result = InstanceofCondition(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TRUE: + jj_consume_token(TRUE); + result = OBooleanExpression.TRUE; + break; + case FALSE: + jj_consume_token(FALSE); + result = OBooleanExpression.FALSE; + break; + default: + jj_la1[190] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBinaryCompareOperator CompareOperator() throws ParseException { + /*@bgen(jjtree) CompareOperator */ + OCompareOperator jjtn000 = new OCompareOperator(JJTCOMPAREOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OBinaryCompareOperator result; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EQ: + case EQEQ: + result = EqualsCompareOperator(); + break; + case LT: + result = LtOperator(); + break; + case GT: + result = GtOperator(); + break; + case NE: + result = NeOperator(); + break; + case NEQ: + result = NeqOperator(); + break; + case GE: + result = GeOperator(); + break; + case LE: + result = LeOperator(); + break; + case LIKE: + result = LikeOperator(); + break; + case CONTAINSKEY: + result = ContainsKeyOperator(); + break; + case LUCENE: + result = LuceneOperator(); + break; + case NEAR: + result = NearOperator(); + break; + case WITHIN: + result = WithinOperator(); + break; + case SC_AND: + result = ScAndOperator(); + break; + default: + jj_la1[191] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return result;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLtOperator LtOperator() throws ParseException { + /*@bgen(jjtree) LtOperator */ + OLtOperator jjtn000 = new OLtOperator(JJTLTOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LT); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OGtOperator GtOperator() throws ParseException { + /*@bgen(jjtree) GtOperator */ + OGtOperator jjtn000 = new OGtOperator(JJTGTOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(GT); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONeOperator NeOperator() throws ParseException { + /*@bgen(jjtree) NeOperator */ + ONeOperator jjtn000 = new ONeOperator(JJTNEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(NE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONeqOperator NeqOperator() throws ParseException { + /*@bgen(jjtree) NeqOperator */ + ONeqOperator jjtn000 = new ONeqOperator(JJTNEQOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(NEQ); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OGeOperator GeOperator() throws ParseException { + /*@bgen(jjtree) GeOperator */ + OGeOperator jjtn000 = new OGeOperator(JJTGEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(GE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLeOperator LeOperator() throws ParseException { + /*@bgen(jjtree) LeOperator */ + OLeOperator jjtn000 = new OLeOperator(JJTLEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLikeOperator LikeOperator() throws ParseException { + /*@bgen(jjtree) LikeOperator */ + OLikeOperator jjtn000 = new OLikeOperator(JJTLIKEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LIKE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLuceneOperator LuceneOperator() throws ParseException { + /*@bgen(jjtree) LuceneOperator */ + OLuceneOperator jjtn000 = new OLuceneOperator(JJTLUCENEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LUCENE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ONearOperator NearOperator() throws ParseException { + /*@bgen(jjtree) NearOperator */ + ONearOperator jjtn000 = new ONearOperator(JJTNEAROPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(NEAR); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OWithinOperator WithinOperator() throws ParseException { + /*@bgen(jjtree) WithinOperator */ + OWithinOperator jjtn000 = new OWithinOperator(JJTWITHINOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(WITHIN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OScAndOperator ScAndOperator() throws ParseException { + /*@bgen(jjtree) ScAndOperator */ + OScAndOperator jjtn000 = new OScAndOperator(JJTSCANDOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(SC_AND); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OContainsKeyOperator ContainsKeyOperator() throws ParseException { + /*@bgen(jjtree) ContainsKeyOperator */ + OContainsKeyOperator jjtn000 = new OContainsKeyOperator(JJTCONTAINSKEYOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CONTAINSKEY); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OContainsValueOperator ContainsValueOperator() throws ParseException { + /*@bgen(jjtree) ContainsValueOperator */ + OContainsValueOperator jjtn000 = new OContainsValueOperator(JJTCONTAINSVALUEOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CONTAINSVALUE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OEqualsCompareOperator EqualsCompareOperator() throws ParseException { + /*@bgen(jjtree) EqualsCompareOperator */ + OEqualsCompareOperator jjtn000 = new OEqualsCompareOperator(JJTEQUALSCOMPAREOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EQ: + jj_consume_token(EQ); + jjtn000.doubleEquals = false; + break; + case EQEQ: + jj_consume_token(EQEQ); + jjtn000.doubleEquals = true; + break; + default: + jj_la1[192] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression BinaryCondition() throws ParseException { + /*@bgen(jjtree) BinaryCondition */ + OBinaryCondition jjtn000 = new OBinaryCondition(JJTBINARYCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Expression(); + jjtn000.operator = CompareOperator(); + jjtn000.right = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ContainsValueCondition() throws ParseException { + /*@bgen(jjtree) ContainsValueCondition */ + OContainsValueCondition jjtn000 = new OContainsValueCondition(JJTCONTAINSVALUECONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Expression(); + jjtn000.operator = ContainsValueOperator(); + if (jj_2_114(3)) { + jj_consume_token(LPAREN); + jjtn000.condition = OrBlock(); + jj_consume_token(RPAREN); + } else if (jj_2_115(2147483647)) { + jjtn000.expression = Expression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression InstanceofCondition() throws ParseException { + /*@bgen(jjtree) InstanceofCondition */ + OInstanceofCondition jjtn000 = new OInstanceofCondition(JJTINSTANCEOFCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + jjtn000.left = Expression(); + jj_consume_token(INSTANCEOF); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.right = Identifier(); + break; + case STRING_LITERAL: + token = jj_consume_token(STRING_LITERAL); + jjtn000.rightString = token.image; + break; + case CHARACTER_LITERAL: + token = jj_consume_token(CHARACTER_LITERAL); + jjtn000.rightString = token.image; + break; + default: + jj_la1[193] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression IndexMatchCondition() throws ParseException { + /*@bgen(jjtree) IndexMatchCondition */ + OIndexMatchCondition jjtn000 = new OIndexMatchCondition(JJTINDEXMATCHCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + jjtn000.leftExpressions = new ArrayList(); + OExpression lastExpression; + try { + jj_consume_token(KEY); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LUCENE: + case NEAR: + case WITHIN: + case EQ: + case EQEQ: + case LT: + case GT: + case LE: + case GE: + case NE: + case NEQ: + case SC_AND: + case LIKE: + case CONTAINSKEY: + jjtn000.operator = CompareOperator(); + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.leftExpressions.add(lastExpression); + label_32: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[194] = jj_gen; + break label_32; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.leftExpressions.add(lastExpression); + } + break; + default: + jj_la1[195] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + case BETWEEN: + jj_consume_token(BETWEEN); + jjtn000.between = true; + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.leftExpressions.add(lastExpression); + label_33: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[196] = jj_gen; + break label_33; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.leftExpressions.add(lastExpression); + } + break; + default: + jj_la1[197] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + jj_consume_token(AND); + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.rightExpressions.add(lastExpression); + label_34: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[198] = jj_gen; + break label_34; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.rightExpressions.add(lastExpression); + } + break; + default: + jj_la1[199] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[200] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression BetweenCondition() throws ParseException { + /*@bgen(jjtree) BetweenCondition */ + OBetweenCondition jjtn000 = new OBetweenCondition(JJTBETWEENCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.first = Expression(); + jj_consume_token(BETWEEN); + jjtn000.second = Expression(); + jj_consume_token(AND); + jjtn000.third = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression IsNullCondition() throws ParseException { + /*@bgen(jjtree) IsNullCondition */ + OIsNullCondition jjtn000 = new OIsNullCondition(JJTISNULLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.expression = Expression(); + jj_consume_token(IS); + jj_consume_token(NULL); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression IsNotNullCondition() throws ParseException { + /*@bgen(jjtree) IsNotNullCondition */ + OIsNotNullCondition jjtn000 = new OIsNotNullCondition(JJTISNOTNULLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.expression = Expression(); + jj_consume_token(IS); + jj_consume_token(NOT); + jj_consume_token(NULL); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression IsDefinedCondition() throws ParseException { + /*@bgen(jjtree) IsDefinedCondition */ + OIsDefinedCondition jjtn000 = new OIsDefinedCondition(JJTISDEFINEDCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.expression = Expression(); + jj_consume_token(IS); + jj_consume_token(DEFINED); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression IsNotDefinedCondition() throws ParseException { + /*@bgen(jjtree) IsNotDefinedCondition */ + OIsNotDefinedCondition jjtn000 = new OIsNotDefinedCondition(JJTISNOTDEFINEDCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.expression = Expression(); + jj_consume_token(IS); + jj_consume_token(NOT); + jj_consume_token(DEFINED); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ContainsCondition() throws ParseException { + /*@bgen(jjtree) ContainsCondition */ + OContainsCondition jjtn000 = new OContainsCondition(JJTCONTAINSCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Expression(); + jj_consume_token(CONTAINS); + if (jj_2_116(3)) { + jj_consume_token(LPAREN); + jjtn000.condition = OrBlock(); + jj_consume_token(RPAREN); + } else if (jj_2_117(2147483647)) { + jjtn000.right = Expression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OInOperator InOperator() throws ParseException { + /*@bgen(jjtree) InOperator */ + OInOperator jjtn000 = new OInOperator(JJTINOPERATOR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(IN); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression InCondition() throws ParseException { + /*@bgen(jjtree) InCondition */ + OInCondition jjtn000 = new OInCondition(JJTINCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression; + try { + jjtn000.left = Expression(); + jjtn000.operator = InOperator(); + if (jj_2_118(2)) { + jj_consume_token(LPAREN); + jjtn000.rightStatement = SelectStatement(); + jj_consume_token(RPAREN); + } else if (jj_2_119(2)) { + jj_consume_token(LPAREN); + jjtn000.rightParam = InputParameter(); + jj_consume_token(RPAREN); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case LPAREN: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.rightMathExpression = MathExpression(); + break; + default: + jj_la1[201] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression NotInCondition() throws ParseException { + /*@bgen(jjtree) NotInCondition */ + ONotInCondition jjtn000 = new ONotInCondition(JJTNOTINCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression; + try { + jjtn000.left = Expression(); + jj_consume_token(NOT); + InOperator(); + if (jj_2_120(2)) { + jj_consume_token(LPAREN); + jjtn000.rightStatement = SelectStatement(); + jj_consume_token(RPAREN); + } else if (jj_2_121(2)) { + jj_consume_token(LPAREN); + jjtn000.rightParam = InputParameter(); + jj_consume_token(RPAREN); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case LPAREN: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.rightMathExpression = MathExpression(); + break; + default: + jj_la1[202] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ContainsAllCondition() throws ParseException { + /*@bgen(jjtree) ContainsAllCondition */ + OContainsAllCondition jjtn000 = new OContainsAllCondition(JJTCONTAINSALLCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Expression(); + jj_consume_token(CONTAINSALL); + if (jj_2_122(3)) { + jj_consume_token(LPAREN); + jjtn000.rightBlock = OrBlock(); + jj_consume_token(RPAREN); + } else if (jj_2_123(2147483647)) { + jjtn000.right = Expression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression ContainsTextCondition() throws ParseException { + /*@bgen(jjtree) ContainsTextCondition */ + OContainsTextCondition jjtn000 = new OContainsTextCondition(JJTCONTAINSTEXTCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.left = Expression(); + jj_consume_token(CONTAINSTEXT); + jjtn000.right = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBooleanExpression MatchesCondition() throws ParseException { + /*@bgen(jjtree) MatchesCondition */ + OMatchesCondition jjtn000 = new OMatchesCondition(JJTMATCHESCONDITION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + jjtn000.expression = Expression(); + jj_consume_token(MATCHES); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STRING_LITERAL: + token = jj_consume_token(STRING_LITERAL); + jjtn000.right = token.image; + break; + case CHARACTER_LITERAL: + token = jj_consume_token(CHARACTER_LITERAL); + jjtn000.right = token.image; + break; + case HOOK: + case COLON: + jjtn000.rightParam = InputParameter(); + break; + default: + jj_la1[203] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OOrderBy OrderBy() throws ParseException { + /*@bgen(jjtree) OrderBy */ + OOrderBy jjtn000 = new OOrderBy(JJTORDERBY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));jjtn000.items = new java.util.ArrayList(); + OOrderByItem lastItem; + OIdentifier lastIdentifier; + OModifier lastModifier; + ORid lastRid; + Token lastToken; + try { + jj_consume_token(ORDER); + jj_consume_token(BY); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case MINUS: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastItem.alias = lastIdentifier.toString(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + lastModifier = Modifier(); + lastItem.modifier = lastModifier; + break; + default: + jj_la1[204] = jj_gen; + ; + } + break; + case INTEGER_LITERAL: + case MINUS: + case 246: + lastItem.rid = Rid(); + break; + case RECORD_ATTRIBUTE: + lastToken = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.recordAttr = lastToken.image; + break; + default: + jj_la1[205] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ASC: + case DESC: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DESC: + jj_consume_token(DESC); + lastItem.type = OOrderByItem.DESC; + break; + case ASC: + jj_consume_token(ASC); + lastItem.type = OOrderByItem.ASC; + break; + default: + jj_la1[206] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[207] = jj_gen; + ; + } + break; + case LPAREN: + jj_consume_token(LPAREN); + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastItem.alias = lastIdentifier.toString(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + lastModifier = Modifier(); + lastItem.modifier = lastModifier; + break; + default: + jj_la1[208] = jj_gen; + ; + } + break; + case INTEGER_LITERAL: + case MINUS: + case 246: + lastItem.rid = Rid(); + break; + case RECORD_ATTRIBUTE: + lastToken = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.recordAttr = lastToken.image; + break; + default: + jj_la1[209] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ASC: + case DESC: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DESC: + jj_consume_token(DESC); + lastItem.type = OOrderByItem.DESC; + break; + case ASC: + jj_consume_token(ASC); + lastItem.type = OOrderByItem.ASC; + break; + default: + jj_la1[210] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[211] = jj_gen; + ; + } + jj_consume_token(RPAREN); + break; + default: + jj_la1[212] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + label_35: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[213] = jj_gen; + break label_35; + } + jj_consume_token(COMMA); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case MINUS: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastItem.alias = lastIdentifier.toString(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + lastModifier = Modifier(); + lastItem.modifier = lastModifier; + break; + default: + jj_la1[214] = jj_gen; + ; + } + break; + case INTEGER_LITERAL: + case MINUS: + case 246: + lastItem.rid = Rid(); + break; + case RECORD_ATTRIBUTE: + lastToken = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.recordAttr = lastToken.image; + break; + default: + jj_la1[215] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ASC: + case DESC: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DESC: + jj_consume_token(DESC); + lastItem.type = OOrderByItem.DESC; + break; + case ASC: + jj_consume_token(ASC); + lastItem.type = OOrderByItem.ASC; + break; + default: + jj_la1[216] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[217] = jj_gen; + ; + } + break; + case LPAREN: + jj_consume_token(LPAREN); + lastItem = new OOrderByItem(); + jjtn000.items.add(lastItem); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastItem.alias = lastIdentifier.toString(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + case DOT: + lastModifier = Modifier(); + lastItem.modifier = lastModifier; + break; + default: + jj_la1[218] = jj_gen; + ; + } + break; + case INTEGER_LITERAL: + case MINUS: + case 246: + lastItem.rid = Rid(); + break; + case RECORD_ATTRIBUTE: + lastToken = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.recordAttr = lastToken.image; + break; + default: + jj_la1[219] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ASC: + case DESC: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DESC: + jj_consume_token(DESC); + lastItem.type = OOrderByItem.DESC; + break; + case ASC: + jj_consume_token(ASC); + lastItem.type = OOrderByItem.ASC; + break; + default: + jj_la1[220] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[221] = jj_gen; + ; + } + jj_consume_token(RPAREN); + break; + default: + jj_la1[222] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OGroupBy GroupBy() throws ParseException { + /*@bgen(jjtree) GroupBy */ + OGroupBy jjtn000 = new OGroupBy(JJTGROUPBY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression; + try { + jj_consume_token(GROUP); + jj_consume_token(BY); + lastExpression = Expression(); + jjtn000.items.add(lastExpression); + label_36: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[223] = jj_gen; + break label_36; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.items.add(lastExpression); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OUnwind Unwind() throws ParseException { + /*@bgen(jjtree) Unwind */ + OUnwind jjtn000 = new OUnwind(JJTUNWIND); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + try { + jj_consume_token(UNWIND); + lastIdentifier = Identifier(); + jjtn000.items.add(lastIdentifier); + label_37: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[224] = jj_gen; + break label_37; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.items.add(lastIdentifier); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLimit Limit() throws ParseException { + /*@bgen(jjtree) Limit */ + OLimit jjtn000 = new OLimit(JJTLIMIT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LIMIT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + jjtn000.num = Integer(); + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + break; + default: + jj_la1[225] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OSkip Skip() throws ParseException { + /*@bgen(jjtree) Skip */ + OSkip jjtn000 = new OSkip(JJTSKIP); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SKIP2: + jj_consume_token(SKIP2); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + jjtn000.num = Integer(); + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + break; + default: + jj_la1[226] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case OFFSET: + jj_consume_token(OFFSET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + jjtn000.num = Integer(); + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + break; + default: + jj_la1[227] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[228] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBatch Batch() throws ParseException { + /*@bgen(jjtree) Batch */ + OBatch jjtn000 = new OBatch(JJTBATCH); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(BATCH); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + jjtn000.num = Integer(); + break; + case HOOK: + case COLON: + jjtn000.inputParam = InputParameter(); + break; + default: + jj_la1[229] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTimeout Timeout() throws ParseException { + /*@bgen(jjtree) Timeout */ + OTimeout jjtn000 = new OTimeout(JJTTIMEOUT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OInteger val; + try { + jj_consume_token(TIMEOUT); + val = Integer(); + jjtn000.val = val.getValue(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + case EXCEPTION: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETURN: + jj_consume_token(RETURN); + jjtn000.failureStrategy = OTimeout.RETURN; + break; + case EXCEPTION: + jj_consume_token(EXCEPTION); + jjtn000.failureStrategy = OTimeout.EXCEPTION; + break; + default: + jj_la1[230] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[231] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public java.lang.Number Wait() throws ParseException { + /*@bgen(jjtree) Wait */ + OWait jjtn000 = new OWait(JJTWAIT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OInteger val; + try { + jj_consume_token(WAIT); + val = Integer(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return val.getValue();} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public java.lang.Number Retry() throws ParseException { + /*@bgen(jjtree) Retry */ + ORetry jjtn000 = new ORetry(JJTRETRY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OInteger val; + try { + jj_consume_token(RETRY); + val = Integer(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return val.getValue();} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCollection Collection() throws ParseException { + /*@bgen(jjtree) Collection */ + OCollection jjtn000 = new OCollection(JJTCOLLECTION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OExpression lastExpression; + try { + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + lastExpression = Expression(); + jjtn000.expressions.add(lastExpression); + label_38: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[232] = jj_gen; + break label_38; + } + jj_consume_token(COMMA); + lastExpression = Expression(); + jjtn000.expressions.add(lastExpression); + } + break; + default: + jj_la1[233] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFetchPlan FetchPlan() throws ParseException { + /*@bgen(jjtree) FetchPlan */ + OFetchPlan jjtn000 = new OFetchPlan(JJTFETCHPLAN); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OFetchPlanItem lastItem; + try { + jj_consume_token(FETCHPLAN); + lastItem = FetchPlanItem(); + jjtn000.items.add(lastItem); + label_39: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case LBRACKET: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + ; + break; + default: + jj_la1[234] = jj_gen; + break label_39; + } + lastItem = FetchPlanItem(); + jjtn000.items.add(lastItem); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFetchPlanItem FetchPlanItem() throws ParseException { + /*@bgen(jjtree) FetchPlanItem */ + OFetchPlanItem jjtn000 = new OFetchPlanItem(JJTFETCHPLANITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + boolean lastStarred = false; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + jjtn000.star = true; + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case LBRACKET: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + jj_consume_token(LBRACKET); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + jjtn000.leftDepth = Integer(); + break; + case STAR: + jj_consume_token(STAR); + jjtn000.leftStar = true; + break; + default: + jj_la1[235] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[236] = jj_gen; + ; + } + lastIdentifier = Identifier(); + lastStarred = false; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + lastStarred = true; + break; + default: + jj_la1[237] = jj_gen; + ; + } + String field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtn000.fieldChain.add(field); + label_40: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + ; + break; + default: + jj_la1[238] = jj_gen; + break label_40; + } + jj_consume_token(DOT); + lastIdentifier = Identifier(); + lastStarred = false; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + lastStarred = true; + break; + default: + jj_la1[239] = jj_gen; + ; + } + field = lastIdentifier.getValue(); + if(lastStarred){ + field += "*"; + } + jjtn000.fieldChain.add(field); + } + break; + default: + jj_la1[240] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(COLON); + jjtn000.rightDepth = Integer(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTraverseProjectionItem TraverseProjectionItem() throws ParseException { + /*@bgen(jjtree) TraverseProjectionItem */ + OTraverseProjectionItem jjtn000 = new OTraverseProjectionItem(JJTTRAVERSEPROJECTIONITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.base = BaseIdentifier(); + if (jj_2_124(2147483647)) { + jjtn000.modifier = Modifier(); + } else { + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OJson Json() throws ParseException { + /*@bgen(jjtree) Json */ + OJson jjtn000 = new OJson(JJTJSON); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OJsonItem lastItem; + Token token; + try { + jj_consume_token(LBRACE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case RECORD_ATTRIBUTE: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastItem = new OJsonItem(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastItem.leftIdentifier = Identifier(); + break; + case RECORD_ATTRIBUTE: + token = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.leftString = token.image; + break; + case STRING_LITERAL: + token = jj_consume_token(STRING_LITERAL); + lastItem.leftString = token.image.substring(1, token.image.length() - 1); + break; + case CHARACTER_LITERAL: + token = jj_consume_token(CHARACTER_LITERAL); + lastItem.leftString = token.image.substring(1, token.image.length() - 1); + break; + default: + jj_la1[241] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(COLON); + lastItem.right = Expression(); + jjtn000.items.add(lastItem); + label_41: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[242] = jj_gen; + break label_41; + } + jj_consume_token(COMMA); + lastItem = new OJsonItem(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastItem.leftIdentifier = Identifier(); + break; + case RECORD_ATTRIBUTE: + token = jj_consume_token(RECORD_ATTRIBUTE); + lastItem.leftString = token.image; + break; + case STRING_LITERAL: + token = jj_consume_token(STRING_LITERAL); + lastItem.leftString = token.image.substring(1, token.image.length() - 1); + break; + case CHARACTER_LITERAL: + token = jj_consume_token(CHARACTER_LITERAL); + lastItem.leftString = token.image.substring(1, token.image.length() - 1); + break; + default: + jj_la1[243] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(COLON); + lastItem.right = Expression(); + jjtn000.items.add(lastItem); + } + break; + default: + jj_la1[244] = jj_gen; + ; + } + jj_consume_token(RBRACE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchExpression MatchExpression() throws ParseException { + /*@bgen(jjtree) MatchExpression */ + OMatchExpression jjtn000 = new OMatchExpression(JJTMATCHEXPRESSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMatchPathItem nextItem = null; + try { + jjtn000.origin = MatchFilter(); + label_42: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + case LT: + case DECR: + case MINUS: + ; + break; + default: + jj_la1[245] = jj_gen; + break label_42; + } + if (jj_2_125(3)) { + nextItem = MatchPathItem(); + } else if (jj_2_126(3)) { + nextItem = MultiMatchPathItemArrows(); + } else if (jj_2_127(3)) { + nextItem = MultiMatchPathItem(); + } else if (jj_2_128(2147483647)) { + nextItem = OutPathItem(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LT: + nextItem = InPathItem(); + break; + default: + jj_la1[246] = jj_gen; + if (jj_2_129(2147483647)) { + nextItem = BothPathItem(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jjtn000.items.add(nextItem); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem MatchPathItem() throws ParseException { + /*@bgen(jjtree) MatchPathItem */ + OMatchPathItem jjtn000 = new OMatchPathItem(JJTMATCHPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.method = MethodCall(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[247] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem MatchPathItemFirst() throws ParseException { + /*@bgen(jjtree) MatchPathItemFirst */ + OMatchPathItemFirst jjtn000 = new OMatchPathItemFirst(JJTMATCHPATHITEMFIRST); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.function = FunctionCall(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[248] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem MultiMatchPathItem() throws ParseException { + /*@bgen(jjtree) MultiMatchPathItem */ + OMultiMatchPathItem jjtn000 = new OMultiMatchPathItem(JJTMULTIMATCHPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMatchPathItem nextItem = null; + try { + jj_consume_token(DOT); + jj_consume_token(LPAREN); + nextItem = MatchPathItemFirst(); + jjtn000.items.add(nextItem); + label_43: + while (true) { + if (jj_2_130(2147483647)) { + ; + } else { + break label_43; + } + nextItem = MatchPathItem(); + jjtn000.items.add(nextItem); + } + jj_consume_token(RPAREN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[249] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem MultiMatchPathItemArrows() throws ParseException { + /*@bgen(jjtree) MultiMatchPathItemArrows */ + OMultiMatchPathItemArrows jjtn000 = new OMultiMatchPathItemArrows(JJTMULTIMATCHPATHITEMARROWS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMatchPathItem prevItem = null; + OMatchPathItem nextItem = null; + try { + jj_consume_token(DOT); + jj_consume_token(LPAREN); + label_44: + while (true) { + if (jj_2_131(2147483647)) { + nextItem = OutPathItemOpt(); + jjtn000.items.add(nextItem); + } else if (jj_2_132(2147483647)) { + nextItem = InPathItemOpt(); + jjtn000.items.add(nextItem); + } else if (jj_2_133(2147483647)) { + nextItem = BothPathItemOpt(); + jjtn000.items.add(nextItem); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + if(prevItem !=null && prevItem.filter == null){ + {if (true) throw new OQueryParsingException("MATCH sub-pattern with no square brackets");} + } + prevItem = nextItem; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LT: + case DECR: + case MINUS: + ; + break; + default: + jj_la1[250] = jj_gen; + break label_44; + } + } + jj_consume_token(RPAREN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[251] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchFilter MatchFilter() throws ParseException { + /*@bgen(jjtree) MatchFilter */ + OMatchFilter jjtn000 = new OMatchFilter(JJTMATCHFILTER); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OMatchFilterItem lastItem = null; + try { + jj_consume_token(LBRACE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case WHERE: + case WHILE: + case AS: + case MAXDEPTH: + case CLASS: + case CLASSES: + case OPTIONAL: + case RID: + lastItem = MatchFilterItem(); + jjtn000.items.add(lastItem); + label_45: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[252] = jj_gen; + break label_45; + } + jj_consume_token(COMMA); + lastItem = MatchFilterItem(); + jjtn000.items.add(lastItem); + } + break; + default: + jj_la1[253] = jj_gen; + ; + } + jj_consume_token(RBRACE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchFilterItem MatchFilterItem() throws ParseException { + /*@bgen(jjtree) MatchFilterItem */ + OMatchFilterItem jjtn000 = new OMatchFilterItem(JJTMATCHFILTERITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLASS: + jj_consume_token(CLASS); + jj_consume_token(COLON); + jjtn000.className = Expression(); + break; + case RID: + jj_consume_token(RID); + jj_consume_token(COLON); + jjtn000.rid = Rid(); + break; + case CLASSES: + jj_consume_token(CLASSES); + jj_consume_token(COLON); + jjtn000.classNames = Expression(); + break; + case AS: + jj_consume_token(AS); + jj_consume_token(COLON); + jjtn000.alias = Identifier(); + break; + case WHERE: + jj_consume_token(WHERE); + jj_consume_token(COLON); + jj_consume_token(LPAREN); + jjtn000.filter = WhereClause(); + jj_consume_token(RPAREN); + break; + case WHILE: + jj_consume_token(WHILE); + jj_consume_token(COLON); + jj_consume_token(LPAREN); + jjtn000.whileCondition = WhereClause(); + jj_consume_token(RPAREN); + break; + case MAXDEPTH: + jj_consume_token(MAXDEPTH); + jj_consume_token(COLON); + jjtn000.maxDepth = Integer(); + break; + case OPTIONAL: + jj_consume_token(OPTIONAL); + jj_consume_token(COLON); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TRUE: + jj_consume_token(TRUE); + jjtn000.optional = true; + break; + case FALSE: + jj_consume_token(FALSE); + jjtn000.optional = false; + break; + default: + jj_la1[254] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[255] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem OutPathItem() throws ParseException { + /*@bgen(jjtree) OutPathItem */ + OOutPathItem jjtn000 = new OOutPathItem(JJTOUTPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[256] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[257] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(GT); + jjtn000.filter = MatchFilter(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "out"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem InPathItem() throws ParseException { + /*@bgen(jjtree) InPathItem */ + OInPathItem jjtn000 = new OInPathItem(JJTINPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + jj_consume_token(LT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[258] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[259] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.filter = MatchFilter(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "in"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem BothPathItem() throws ParseException { + /*@bgen(jjtree) BothPathItem */ + OBothPathItem jjtn000 = new OBothPathItem(JJTBOTHPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[260] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[261] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.filter = MatchFilter(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "both"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem OutPathItemOpt() throws ParseException { + /*@bgen(jjtree) OutPathItemOpt */ + OOutPathItemOpt jjtn000 = new OOutPathItemOpt(JJTOUTPATHITEMOPT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[262] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[263] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(GT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[264] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "out"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem InPathItemOpt() throws ParseException { + /*@bgen(jjtree) InPathItemOpt */ + OInPathItemOpt jjtn000 = new OInPathItemOpt(JJTINPATHITEMOPT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + jj_consume_token(LT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[265] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[266] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[267] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "in"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OMatchPathItem BothPathItemOpt() throws ParseException { + /*@bgen(jjtree) BothPathItemOpt */ + OBothPathItemOpt jjtn000 = new OBothPathItemOpt(JJTBOTHPATHITEMOPT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier edgeName = null; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + jj_consume_token(MINUS); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + edgeName = Identifier(); + break; + default: + jj_la1[268] = jj_gen; + ; + } + jj_consume_token(MINUS); + break; + case DECR: + jj_consume_token(DECR); + break; + default: + jj_la1[269] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACE: + jjtn000.filter = MatchFilter(); + break; + default: + jj_la1[270] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + if(edgeName==null){ + edgeName = new OIdentifier(-1); + edgeName.value = "E"; + } + jjtn000.method = new OMethodCall(-1); + jjtn000.method.methodName = new OIdentifier(-1); + jjtn000.method.methodName.value = "both"; + OExpression exp = new OExpression(-1); + exp.value = edgeName.value; + jjtn000.method.params.add(exp); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OProfileStorageStatement ProfileStorageStatement() throws ParseException { + /*@bgen(jjtree) ProfileStorageStatement */ + OProfileStorageStatement jjtn000 = new OProfileStorageStatement(JJTPROFILESTORAGESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(PROFILE); + jj_consume_token(STORAGE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ON: + jj_consume_token(ON); + jjtn000.on = true; + break; + case OFF: + jj_consume_token(OFF); + jjtn000.on = false; + break; + default: + jj_la1[271] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTruncateClassStatement TruncateClassStatement() throws ParseException { + /*@bgen(jjtree) TruncateClassStatement */ + OTruncateClassStatement jjtn000 = new OTruncateClassStatement(JJTTRUNCATECLASSSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(TRUNCATE); + jj_consume_token(CLASS); + jjtn000.className = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case POLYMORPHIC: + jj_consume_token(POLYMORPHIC); + jjtn000.polymorphic = true; + break; + default: + jj_la1[272] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[273] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTruncateClusterStatement TruncateClusterStatement() throws ParseException { + /*@bgen(jjtree) TruncateClusterStatement */ + OTruncateClusterStatement jjtn000 = new OTruncateClusterStatement(JJTTRUNCATECLUSTERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(TRUNCATE); + jj_consume_token(CLUSTER); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.clusterName = Identifier(); + break; + case INTEGER_LITERAL: + case MINUS: + jjtn000.clusterNumber = Integer(); + break; + default: + jj_la1[274] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[275] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OTruncateRecordStatement TruncateRecordStatement() throws ParseException { + /*@bgen(jjtree) TruncateRecordStatement */ + OTruncateRecordStatement jjtn000 = new OTruncateRecordStatement(JJTTRUNCATERECORDSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));ORid lastRecord; + try { + jj_consume_token(TRUNCATE); + jj_consume_token(RECORD); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.record = Rid(); + break; + case LBRACKET: + jj_consume_token(LBRACKET); + jjtn000.records = new ArrayList(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + lastRecord = Rid(); + jjtn000.records.add(lastRecord); + label_46: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[276] = jj_gen; + break label_46; + } + jj_consume_token(COMMA); + lastRecord = Rid(); + jjtn000.records.add(lastRecord); + } + break; + default: + jj_la1[277] = jj_gen; + ; + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[278] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OFindReferencesStatement FindReferencesStatement() throws ParseException { + /*@bgen(jjtree) FindReferencesStatement */ + OFindReferencesStatement jjtn000 = new OFindReferencesStatement(JJTFINDREFERENCESSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));SimpleNode lastTarget; + try { + jj_consume_token(FIND); + jj_consume_token(REFERENCES); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INTEGER_LITERAL: + case MINUS: + case 246: + jjtn000.rid = Rid(); + break; + case LPAREN: + jj_consume_token(LPAREN); + jjtn000.subQuery = StatementInternal(); + jj_consume_token(RPAREN); + break; + default: + jj_la1[279] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LBRACKET: + jj_consume_token(LBRACKET); + jjtn000.targets = new ArrayList(); + if (jj_2_134(2147483647)) { + lastTarget = IndexIdentifier(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastTarget = Identifier(); + break; + default: + jj_la1[280] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtn000.targets.add(lastTarget); + label_47: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[281] = jj_gen; + break label_47; + } + jj_consume_token(COMMA); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastTarget = Identifier(); + break; + case CLUSTER_IDENTIFIER: + case CLUSTER_NUMBER_IDENTIFIER: + lastTarget = Cluster(); + break; + default: + jj_la1[282] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.targets.add(lastTarget); + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[283] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateClassStatement CreateClassStatement() throws ParseException { + /*@bgen(jjtree) CreateClassStatement */ + OCreateClassStatement jjtn000 = new OCreateClassStatement(JJTCREATECLASSSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + OInteger lastInteger; + try { + jj_consume_token(CREATE); + jj_consume_token(CLASS); + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IF: + jj_consume_token(IF); + jj_consume_token(NOT); + jj_consume_token(EXISTS); + jjtn000.ifNotExists = true; + break; + default: + jj_la1[284] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EXTENDS: + jj_consume_token(EXTENDS); + lastIdentifier = Identifier(); + jjtn000.superclasses = new ArrayList(); jjtn000.superclasses.add(lastIdentifier); + label_48: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[285] = jj_gen; + break label_48; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.superclasses.add(lastIdentifier); + } + break; + default: + jj_la1[286] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + lastInteger = Integer(); + jjtn000.clusters = new ArrayList(); jjtn000.clusters.add(lastInteger); + label_49: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[287] = jj_gen; + break label_49; + } + jj_consume_token(COMMA); + lastInteger = Integer(); + jjtn000.clusters.add(lastInteger); + } + break; + default: + jj_la1[288] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTERS: + jj_consume_token(CLUSTERS); + jjtn000.totalClusterNo = Integer(); + break; + default: + jj_la1[289] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ABSTRACT: + jj_consume_token(ABSTRACT); + jjtn000.abstractClass = true; + break; + default: + jj_la1[290] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAlterClassStatement AlterClassStatement() throws ParseException { + /*@bgen(jjtree) AlterClassStatement */ + OAlterClassStatement jjtn000 = new OAlterClassStatement(JJTALTERCLASSSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + OInteger lastInteger; + Token lastToken; + try { + jj_consume_token(ALTER); + jj_consume_token(CLASS); + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case NAME: + jj_consume_token(NAME); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.NAME; + jjtn000.identifierValue = Identifier(); + break; + case SHORTNAME: + jj_consume_token(SHORTNAME); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SHORTNAME; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case NULL: + jj_consume_token(NULL); + break; + default: + jj_la1[291] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case SUPERCLASS: + jj_consume_token(SUPERCLASS); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SUPERCLASS; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PLUS: + case MINUS: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PLUS: + jj_consume_token(PLUS); + jjtn000.add = true; + break; + case MINUS: + jj_consume_token(MINUS); + jjtn000.remove = true; + break; + default: + jj_la1[292] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[293] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case NULL: + jj_consume_token(NULL); + jjtn000.identifierValue = null; + break; + default: + jj_la1[294] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case SUPERCLASSES: + jj_consume_token(SUPERCLASSES); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.SUPERCLASSES; + jjtn000.identifierListValue = new ArrayList(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + jjtn000.identifierListValue.add(lastIdentifier); + label_50: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[295] = jj_gen; + break label_50; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.identifierListValue.add(lastIdentifier); + } + break; + case NULL: + jj_consume_token(NULL); + jjtn000.identifierListValue = null; + break; + default: + jj_la1[296] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case OVERSIZE: + jj_consume_token(OVERSIZE); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.OVERSIZE; + jjtn000.numberValue = Number(); + break; + case STRICTMODE: + jj_consume_token(STRICTMODE); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.STRICTMODE; + jjtn000.expression = Expression(); + break; + case ADDCLUSTER: + jj_consume_token(ADDCLUSTER); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ADDCLUSTER; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case INTEGER_LITERAL: + case MINUS: + jjtn000.numberValue = Integer(); + break; + default: + jj_la1[297] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case REMOVECLUSTER: + jj_consume_token(REMOVECLUSTER); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.REMOVECLUSTER; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case INTEGER_LITERAL: + case MINUS: + jjtn000.numberValue = Integer(); + break; + default: + jj_la1[298] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case CUSTOM: + jj_consume_token(CUSTOM); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.CUSTOM; + jjtn000.customKey = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case EQ: + jj_consume_token(EQ); + jjtn000.customValue = Expression(); + break; + default: + jj_la1[299] = jj_gen; + ; + } + break; + case ABSTRACT: + jj_consume_token(ABSTRACT); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ABSTRACT; + jjtn000.expression = Expression(); + break; + case CLUSTERSELECTION: + jj_consume_token(CLUSTERSELECTION); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.CLUSTERSELECTION; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case 248: + jj_consume_token(248); + jjtn000.customString = "round-robin"; + break; + case STRING_LITERAL: + lastToken = jj_consume_token(STRING_LITERAL); + jjtn000.customString = token.image.substring(1, token.image.length() - 1); + break; + default: + jj_la1[300] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + case DESCRIPTION: + jj_consume_token(DESCRIPTION); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.DESCRIPTION; + jjtn000.expression = Expression(); + break; + case ENCRYPTION: + jj_consume_token(ENCRYPTION); + jjtn000.property = com.orientechnologies.orient.core.metadata.schema.OClass.ATTRIBUTES.ENCRYPTION; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifierValue = Identifier(); + break; + case NULL: + jj_consume_token(NULL); + break; + default: + jj_la1[301] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[302] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[303] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODropClassStatement DropClassStatement() throws ParseException { + /*@bgen(jjtree) DropClassStatement */ + ODropClassStatement jjtn000 = new ODropClassStatement(JJTDROPCLASSSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DROP); + jj_consume_token(CLASS); + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IF: + jj_consume_token(IF); + jj_consume_token(EXISTS); + jjtn000.ifExists = true; + break; + default: + jj_la1[304] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[305] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public void IfNotExists() throws ParseException { + /*@bgen(jjtree) IfNotExists */ + OIfNotExists jjtn000 = new OIfNotExists(JJTIFNOTEXISTS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(IF); + jj_consume_token(NOT); + jj_consume_token(EXISTS); + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + } + + final public OCreatePropertyStatement CreatePropertyStatement() throws ParseException { + /*@bgen(jjtree) CreatePropertyStatement */ + OCreatePropertyStatement jjtn000 = new OCreatePropertyStatement(JJTCREATEPROPERTYSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OCreatePropertyAttributeStatement lastAttribute; + try { + jj_consume_token(CREATE); + jj_consume_token(PROPERTY); + jjtn000.className = Identifier(); + jj_consume_token(DOT); + jjtn000.propertyName = Identifier(); + if (jj_2_135(3)) { + IfNotExists(); + jjtn000.ifNotExists = true; + } else { + ; + } + jjtn000.propertyType = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.linkedType = Identifier(); + break; + default: + jj_la1[306] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LPAREN: + jj_consume_token(LPAREN); + lastAttribute = CreatePropertyAttributeStatement(); + jjtn000.attributes.add(lastAttribute); + label_51: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[307] = jj_gen; + break label_51; + } + jj_consume_token(COMMA); + lastAttribute = CreatePropertyAttributeStatement(); + jjtn000.attributes.add(lastAttribute); + } + jj_consume_token(RPAREN); + break; + default: + jj_la1[308] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case UNSAFE: + jj_consume_token(UNSAFE); + jjtn000.unsafe = true; + break; + default: + jj_la1[309] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreatePropertyAttributeStatement CreatePropertyAttributeStatement() throws ParseException { + /*@bgen(jjtree) CreatePropertyAttributeStatement */ + OCreatePropertyAttributeStatement jjtn000 = new OCreatePropertyAttributeStatement(JJTCREATEPROPERTYATTRIBUTESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jjtn000.settingName = Identifier(); + if (getToken(1).kind != COMMA && getToken(1).kind != RPAREN) { + jjtn000.settingValue = Expression(); + } else { + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAlterPropertyStatement AlterPropertyStatement() throws ParseException { + /*@bgen(jjtree) AlterPropertyStatement */ + OAlterPropertyStatement jjtn000 = new OAlterPropertyStatement(JJTALTERPROPERTYSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(ALTER); + jj_consume_token(PROPERTY); + jjtn000.className = Identifier(); + jj_consume_token(DOT); + jjtn000.propertyName = Identifier(); + if (jj_2_136(3)) { + jj_consume_token(CUSTOM); + jjtn000.customPropertyName = Identifier(); + jj_consume_token(EQ); + jjtn000.customPropertyValue = Expression(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.settingName = Identifier(); + jjtn000.settingValue = Expression(); + if(jjtn000.settingName.getStringValue().equalsIgnoreCase("custom") && jjtn000.settingValue.toString().equalsIgnoreCase("clear")){ + jjtn000.settingName = null; + jjtn000.settingValue = null; + jjtn000.clearCustom = true; + } + break; + default: + jj_la1[310] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODropPropertyStatement DropPropertyStatement() throws ParseException { + /*@bgen(jjtree) DropPropertyStatement */ + ODropPropertyStatement jjtn000 = new ODropPropertyStatement(JJTDROPPROPERTYSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DROP); + jj_consume_token(PROPERTY); + jjtn000.className = Identifier(); + jj_consume_token(DOT); + jjtn000.propertyName = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IF: + jj_consume_token(IF); + jj_consume_token(EXISTS); + jjtn000.ifExists = true; + break; + default: + jj_la1[311] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FORCE: + jj_consume_token(FORCE); + jjtn000.force = true; + break; + default: + jj_la1[312] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateIndexStatement CreateIndexStatement() throws ParseException { + /*@bgen(jjtree) CreateIndexStatement */ + OCreateIndexStatement jjtn000 = new OCreateIndexStatement(JJTCREATEINDEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OCreateIndexStatement.Property lastProperty; + OIdentifier lastIdentifier; + ORecordAttribute lastRecordAttr; + try { + jj_consume_token(CREATE); + jj_consume_token(INDEX); + jjtn000.name = IndexName(); + if (jj_2_137(3)) { + jj_consume_token(ON); + jjtn000.className = Identifier(); + jj_consume_token(LPAREN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.name = lastIdentifier; + jjtn000.propertyList.add(lastProperty); + break; + case RECORD_ATTRIBUTE: + lastRecordAttr = RecordAttribute(); + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.recordAttribute = lastRecordAttr; + jjtn000.propertyList.add(lastProperty); + break; + default: + jj_la1[313] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BY: + jj_consume_token(BY); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case KEY: + jj_consume_token(KEY); + lastProperty.byKey = true; + break; + case VALUE: + jj_consume_token(VALUE); + lastProperty.byValue = true; + break; + default: + jj_la1[314] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[315] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COLLATE: + jj_consume_token(COLLATE); + lastProperty.collate = Identifier(); + break; + default: + jj_la1[316] = jj_gen; + ; + } + label_52: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[317] = jj_gen; + break label_52; + } + jj_consume_token(COMMA); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.name = lastIdentifier; + jjtn000.propertyList.add(lastProperty); + break; + case RECORD_ATTRIBUTE: + lastRecordAttr = RecordAttribute(); + lastProperty = new OCreateIndexStatement.Property(); + lastProperty.recordAttribute = lastRecordAttr; + jjtn000.propertyList.add(lastProperty); + break; + default: + jj_la1[318] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case BY: + jj_consume_token(BY); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case KEY: + jj_consume_token(KEY); + lastProperty.byKey = true; + break; + case VALUE: + jj_consume_token(VALUE); + lastProperty.byValue = true; + break; + default: + jj_la1[319] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[320] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COLLATE: + jj_consume_token(COLLATE); + lastProperty.collate = Identifier(); + break; + default: + jj_la1[321] = jj_gen; + ; + } + } + jj_consume_token(RPAREN); + jjtn000.type = Identifier(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.type = Identifier(); + break; + default: + jj_la1[322] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + if (jj_2_140(2)) { + jj_consume_token(ENGINE); + jjtn000.engine = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + if (jj_2_138(2)) { + jj_consume_token(METADATA); + jjtn000.metadata = Json(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + jjtn000.keyTypes.add(lastIdentifier); + label_53: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[323] = jj_gen; + break label_53; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.keyTypes.add(lastIdentifier); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case METADATA: + jj_consume_token(METADATA); + jjtn000.metadata = Json(); + break; + default: + jj_la1[324] = jj_gen; + ; + } + break; + default: + jj_la1[325] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + break; + default: + jj_la1[326] = jj_gen; + ; + } + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + if (jj_2_139(2)) { + jj_consume_token(METADATA); + jjtn000.metadata = Json(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + lastIdentifier = Identifier(); + jjtn000.keyTypes.add(lastIdentifier); + label_54: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[327] = jj_gen; + break label_54; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.keyTypes.add(lastIdentifier); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case METADATA: + jj_consume_token(METADATA); + jjtn000.metadata = Json(); + break; + default: + jj_la1[328] = jj_gen; + ; + } + break; + default: + jj_la1[329] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + break; + default: + jj_la1[330] = jj_gen; + ; + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ORebuildIndexStatement RebuildIndexStatement() throws ParseException { + /*@bgen(jjtree) RebuildIndexStatement */ + ORebuildIndexStatement jjtn000 = new ORebuildIndexStatement(JJTREBUILDINDEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(REBUILD); + jj_consume_token(INDEX); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 247: + jjtn000.name = IndexName(); + break; + case STAR: + jj_consume_token(STAR); + jjtn000.all = true; + break; + default: + jj_la1[331] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODropIndexStatement DropIndexStatement() throws ParseException { + /*@bgen(jjtree) DropIndexStatement */ + ODropIndexStatement jjtn000 = new ODropIndexStatement(JJTDROPINDEXSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DROP); + jj_consume_token(INDEX); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 247: + jjtn000.name = IndexName(); + break; + case STAR: + jj_consume_token(STAR); + jjtn000.all = true; + break; + default: + jj_la1[332] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateClusterStatement CreateClusterStatement() throws ParseException { + /*@bgen(jjtree) CreateClusterStatement */ + OCreateClusterStatement jjtn000 = new OCreateClusterStatement(JJTCREATECLUSTERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + break; + case BLOB: + jj_consume_token(BLOB); + jj_consume_token(CLUSTER); + jjtn000.blob = true; + break; + default: + jj_la1[333] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ID: + jj_consume_token(ID); + jjtn000.id = Integer(); + break; + default: + jj_la1[334] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAlterClusterStatement AlterClusterStatement() throws ParseException { + /*@bgen(jjtree) AlterClusterStatement */ + OAlterClusterStatement jjtn000 = new OAlterClusterStatement(JJTALTERCLUSTERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(ALTER); + jj_consume_token(CLUSTER); + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case STAR: + jj_consume_token(STAR); + jjtn000.starred = true; + break; + default: + jj_la1[335] = jj_gen; + ; + } + jjtn000.attributeName = Identifier(); + jjtn000.attributeValue = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODropClusterStatement DropClusterStatement() throws ParseException { + /*@bgen(jjtree) DropClusterStatement */ + ODropClusterStatement jjtn000 = new ODropClusterStatement(JJTDROPCLUSTERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(DROP); + jj_consume_token(CLUSTER); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.name = Identifier(); + break; + case INTEGER_LITERAL: + case MINUS: + jjtn000.id = Integer(); + break; + default: + jj_la1[336] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAlterDatabaseStatement AlterDatabaseStatement() throws ParseException { + /*@bgen(jjtree) AlterDatabaseStatement */ + OAlterDatabaseStatement jjtn000 = new OAlterDatabaseStatement(JJTALTERDATABASESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(ALTER); + jj_consume_token(DATABASE); + if (jj_2_141(3)) { + jj_consume_token(CUSTOM); + jjtn000.customPropertyName = Identifier(); + jj_consume_token(EQ); + jjtn000.customPropertyValue = Expression(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.settingName = Identifier(); + jjtn000.settingValue = Expression(); + break; + default: + jj_la1[337] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCommandLineOption CommandLineOption() throws ParseException { + /*@bgen(jjtree) CommandLineOption */ + OCommandLineOption jjtn000 = new OCommandLineOption(JJTCOMMANDLINEOPTION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(MINUS); + jjtn000.name = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OOptimizeDatabaseStatement OptimizeDatabaseStatement() throws ParseException { + /*@bgen(jjtree) OptimizeDatabaseStatement */ + OOptimizeDatabaseStatement jjtn000 = new OOptimizeDatabaseStatement(JJTOPTIMIZEDATABASESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OCommandLineOption lastOption; + try { + jj_consume_token(OPTIMIZE); + jj_consume_token(DATABASE); + label_55: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case MINUS: + ; + break; + default: + jj_la1[338] = jj_gen; + break label_55; + } + lastOption = CommandLineOption(); + jjtn000.options.add(lastOption); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateLinkStatement CreateLinkStatement() throws ParseException { + /*@bgen(jjtree) CreateLinkStatement */ + OCreateLinkStatement jjtn000 = new OCreateLinkStatement(JJTCREATELINKSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CREATE); + jj_consume_token(LINK); + jjtn000.name = Identifier(); + jj_consume_token(TYPE); + jjtn000.type = Identifier(); + jj_consume_token(FROM); + jjtn000.sourceClass = Identifier(); + jj_consume_token(DOT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.sourceField = Identifier(); + break; + case RECORD_ATTRIBUTE: + jjtn000.sourceRecordAttr = RecordAttribute(); + break; + default: + jj_la1[339] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jj_consume_token(TO); + jjtn000.destClass = Identifier(); + jj_consume_token(DOT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.destField = Identifier(); + break; + case RECORD_ATTRIBUTE: + jjtn000.destRecordAttr = RecordAttribute(); + break; + default: + jj_la1[340] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INVERSE: + jj_consume_token(INVERSE); + jjtn000.inverse = true; + break; + default: + jj_la1[341] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OExplainStatement ExplainStatement() throws ParseException { + /*@bgen(jjtree) ExplainStatement */ + OExplainStatement jjtn000 = new OExplainStatement(JJTEXPLAINSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(EXPLAIN); + jjtn000.statement = StatementInternal(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OPermission Permission() throws ParseException { + /*@bgen(jjtree) Permission */ + OPermission jjtn000 = new OPermission(JJTPERMISSION); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CREATE: + jj_consume_token(CREATE); + jjtn000.permission = "CREATE"; + break; + case READ: + jj_consume_token(READ); + jjtn000.permission = "READ"; + break; + case UPDATE: + jj_consume_token(UPDATE); + jjtn000.permission = "UPDATE"; + break; + case DELETE: + jj_consume_token(DELETE); + jjtn000.permission = "DELETE"; + break; + case EXECUTE: + jj_consume_token(EXECUTE); + jjtn000.permission = "EXECUTE"; + break; + case ALL: + jj_consume_token(ALL); + jjtn000.permission = "ALL"; + break; + case NONE: + jj_consume_token(NONE); + jjtn000.permission = "NONE"; + break; + default: + jj_la1[342] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OResourcePathItem ResourcePathItem() throws ParseException { + /*@bgen(jjtree) ResourcePathItem */ + OResourcePathItem jjtn000 = new OResourcePathItem(JJTRESOURCEPATHITEM); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CLUSTER: + jj_consume_token(CLUSTER); + jjtn000.name = "cluster"; + break; + case STAR: + jj_consume_token(STAR); + jjtn000.star = true; + break; + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + jjtn000.identifier = Identifier(); + break; + default: + jj_la1[343] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OGrantStatement GrantStatement() throws ParseException { + /*@bgen(jjtree) GrantStatement */ + OGrantStatement jjtn000 = new OGrantStatement(JJTGRANTSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OResourcePathItem lastItem; + try { + jj_consume_token(GRANT); + jjtn000.permission = Permission(); + jj_consume_token(ON); + lastItem = ResourcePathItem(); + jjtn000.resourceChain.add(lastItem); + label_56: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + ; + break; + default: + jj_la1[344] = jj_gen; + break label_56; + } + jj_consume_token(DOT); + lastItem = ResourcePathItem(); + jjtn000.resourceChain.add(lastItem); + } + jj_consume_token(TO); + jjtn000.actor = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ORevokeStatement RevokeStatement() throws ParseException { + /*@bgen(jjtree) RevokeStatement */ + ORevokeStatement jjtn000 = new ORevokeStatement(JJTREVOKESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OResourcePathItem lastItem; + try { + jj_consume_token(REVOKE); + jjtn000.permission = Permission(); + jj_consume_token(ON); + lastItem = ResourcePathItem(); + jjtn000.resourceChain.add(lastItem); + label_57: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOT: + ; + break; + default: + jj_la1[345] = jj_gen; + break label_57; + } + jj_consume_token(DOT); + lastItem = ResourcePathItem(); + jjtn000.resourceChain.add(lastItem); + } + jj_consume_token(FROM); + jjtn000.actor = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateFunctionStatement CreateFunctionStatement() throws ParseException { + /*@bgen(jjtree) CreateFunctionStatement */ + OCreateFunctionStatement jjtn000 = new OCreateFunctionStatement(JJTCREATEFUNCTIONSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + OIdentifier lastIdentifier; + try { + jj_consume_token(CREATE); + jj_consume_token(FUNCTION); + jjtn000.name = Identifier(); + token = jj_consume_token(STRING_LITERAL); + jjtn000.codeQuoted = token.image; + jjtn000.code = token.image.substring(1, token.image.length() -1); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case PARAMETERS: + jj_consume_token(PARAMETERS); + jj_consume_token(LBRACKET); + lastIdentifier = Identifier(); + jjtn000.parameters = new ArrayList(); + jjtn000.parameters.add(lastIdentifier); + label_58: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case COMMA: + ; + break; + default: + jj_la1[346] = jj_gen; + break label_58; + } + jj_consume_token(COMMA); + lastIdentifier = Identifier(); + jjtn000.parameters.add(lastIdentifier); + } + jj_consume_token(RBRACKET); + break; + default: + jj_la1[347] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IDEMPOTENT: + jj_consume_token(IDEMPOTENT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TRUE: + jj_consume_token(TRUE); + jjtn000.idempotent = true; + break; + case FALSE: + jj_consume_token(FALSE); + jjtn000.idempotent = false; + break; + default: + jj_la1[348] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[349] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case LANGUAGE: + jj_consume_token(LANGUAGE); + jjtn000.language = Identifier(); + break; + default: + jj_la1[350] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OLetStatement LetStatement() throws ParseException { + /*@bgen(jjtree) LetStatement */ + OLetStatement jjtn000 = new OLetStatement(JJTLETSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(LET); + jjtn000.name = Identifier(); + jj_consume_token(EQ); + if (jj_2_142(2147483647)) { + jjtn000.statement = StatementInternal(); + } else if (jj_2_143(2147483647)) { + jjtn000.expression = Expression(); + } else { + jj_consume_token(-1); + throw new ParseException(); + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OBeginStatement BeginStatement() throws ParseException { + /*@bgen(jjtree) BeginStatement */ + OBeginStatement jjtn000 = new OBeginStatement(JJTBEGINSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(BEGIN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ISOLATION: + jj_consume_token(ISOLATION); + jjtn000.isolation = Identifier(); + break; + default: + jj_la1[351] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCommitStatement CommitStatement() throws ParseException { + /*@bgen(jjtree) CommitStatement */ + OCommitStatement jjtn000 = new OCommitStatement(JJTCOMMITSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(COMMIT); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case RETRY: + jj_consume_token(RETRY); + jjtn000.retry = Integer(); + break; + default: + jj_la1[352] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ORollbackStatement RollbackStatement() throws ParseException { + /*@bgen(jjtree) RollbackStatement */ + ORollbackStatement jjtn000 = new ORollbackStatement(JJTROLLBACKSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(ROLLBACK); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OReturnStatement ReturnStatement() throws ParseException { + /*@bgen(jjtree) ReturnStatement */ + OReturnStatement jjtn000 = new OReturnStatement(JJTRETURNSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(RETURN); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case TO: + case VALUE: + case VALUES: + case SET: + case ADD: + case PUT: + case MERGE: + case CONTENT: + case REMOVE: + case NULL: + case ORDER: + case GROUP: + case OFFSET: + case RECORD: + case CACHE: + case LUCENE: + case NEAR: + case WITHIN: + case MINDEPTH: + case CLASS: + case SUPERCLASS: + case CLASSES: + case SUPERCLASSES: + case EXCEPTION: + case PROFILE: + case STORAGE: + case ON: + case OFF: + case TRUNCATE: + case FIND: + case REFERENCES: + case EXTENDS: + case CLUSTERS: + case ABSTRACT: + case ALTER: + case NAME: + case SHORTNAME: + case OVERSIZE: + case STRICTMODE: + case ADDCLUSTER: + case REMOVECLUSTER: + case CUSTOM: + case CLUSTERSELECTION: + case DESCRIPTION: + case ENCRYPTION: + case DROP: + case PROPERTY: + case FORCE: + case METADATA: + case INDEX: + case COLLATE: + case ENGINE: + case REBUILD: + case ID: + case DATABASE: + case OPTIMIZE: + case LINK: + case TYPE: + case INVERSE: + case EXPLAIN: + case GRANT: + case REVOKE: + case READ: + case EXECUTE: + case ALL: + case NONE: + case FUNCTION: + case PARAMETERS: + case IDEMPOTENT: + case LANGUAGE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case ISOLATION: + case SLEEP: + case CONSOLE: + case BLOB: + case SHARED: + case DEFAULT_: + case SEQUENCE: + case START: + case OPTIONAL: + case COUNT: + case HA: + case STATUS: + case SERVER: + case SYNC: + case EXISTS: + case RID: + case RIDS: + case MOVE: + case THIS: + case RECORD_ATTRIBUTE: + case INTEGER_LITERAL: + case FLOATING_POINT_LITERAL: + case CHARACTER_LITERAL: + case STRING_LITERAL: + case TRUE: + case FALSE: + case LPAREN: + case LBRACE: + case LBRACKET: + case HOOK: + case COLON: + case MINUS: + case STAR: + case IN: + case KEY: + case IDENTIFIER: + case QUOTED_IDENTIFIER: + case 246: + jjtn000.expression = Expression(); + break; + default: + jj_la1[353] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OIfStatement IfStatement() throws ParseException { + /*@bgen(jjtree) IfStatement */ + OIfStatement jjtn000 = new OIfStatement(JJTIFSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OStatement last; + try { + jj_consume_token(IF); + jj_consume_token(LPAREN); + jjtn000.expression = OrBlock(); + jj_consume_token(RPAREN); + jj_consume_token(LBRACE); + label_59: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case SELECT: + case TRAVERSE: + case MATCH: + case INSERT: + case CREATE: + case DELETE: + case UPDATE: + case RETURN: + case LET: + case PROFILE: + case TRUNCATE: + case FIND: + case ALTER: + case DROP: + case REBUILD: + case OPTIMIZE: + case EXPLAIN: + case GRANT: + case REVOKE: + case BEGIN: + case COMMIT: + case ROLLBACK: + case IF: + case SLEEP: + case CONSOLE: + case HA: + case MOVE: + case SEMICOLON: + ; + break; + default: + jj_la1[354] = jj_gen; + break label_59; + } + if (jj_2_144(2147483647)) { + last = StatementSemicolon(); + jjtn000.statements.add(last); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case IF: + last = IfStatement(); + jjtn000.statements.add(last); + break; + case SEMICOLON: + jj_consume_token(SEMICOLON); + break; + default: + jj_la1[355] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } + jj_consume_token(RBRACE); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OSleepStatement SleepStatement() throws ParseException { + /*@bgen(jjtree) SleepStatement */ + OSleepStatement jjtn000 = new OSleepStatement(JJTSLEEPSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(SLEEP); + jjtn000.millis = Integer(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OConsoleStatement ConsoleStatement() throws ParseException { + /*@bgen(jjtree) ConsoleStatement */ + OConsoleStatement jjtn000 = new OConsoleStatement(JJTCONSOLESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(CONSOLE); + jj_consume_token(DOT); + jjtn000.logLevel = Identifier(); + jjtn000.message = Expression(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OCreateSequenceStatement CreateSequenceStatement() throws ParseException { + /*@bgen(jjtree) CreateSequenceStatement */ + OCreateSequenceStatement jjtn000 = new OCreateSequenceStatement(JJTCREATESEQUENCESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + try { + jj_consume_token(CREATE); + jj_consume_token(SEQUENCE); + jjtn000.name = Identifier(); + jj_consume_token(TYPE); + lastIdentifier = Identifier(); + if(lastIdentifier.getStringValue().equalsIgnoreCase("cached")){ + jjtn000.type = OCreateSequenceStatement.TYPE_CACHED; + }else if(lastIdentifier.getStringValue().equalsIgnoreCase("ordered")){ + jjtn000.type = OCreateSequenceStatement.TYPE_ORDERED; + }else{ + {if (true) throw new ParseException();} + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case START: + jj_consume_token(START); + jjtn000.start = Expression(); + break; + default: + jj_la1[356] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INCREMENT: + jj_consume_token(INCREMENT); + jjtn000.increment = Expression(); + break; + default: + jj_la1[357] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CACHE: + jj_consume_token(CACHE); + jjtn000.cache = Expression(); + break; + default: + jj_la1[358] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OAlterSequenceStatement AlterSequenceStatement() throws ParseException { + /*@bgen(jjtree) AlterSequenceStatement */ + OAlterSequenceStatement jjtn000 = new OAlterSequenceStatement(JJTALTERSEQUENCESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + try { + jj_consume_token(ALTER); + jj_consume_token(SEQUENCE); + jjtn000.name = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case START: + jj_consume_token(START); + jjtn000.start = Expression(); + break; + default: + jj_la1[359] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case INCREMENT: + jj_consume_token(INCREMENT); + jjtn000.increment = Expression(); + break; + default: + jj_la1[360] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case CACHE: + jj_consume_token(CACHE); + jjtn000.cache = Expression(); + break; + default: + jj_la1[361] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public ODropSequenceStatement DropSequenceStatement() throws ParseException { + /*@bgen(jjtree) DropSequenceStatement */ + ODropSequenceStatement jjtn000 = new ODropSequenceStatement(JJTDROPSEQUENCESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));OIdentifier lastIdentifier; + try { + jj_consume_token(DROP); + jj_consume_token(SEQUENCE); + jjtn000.name = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OHaStatusStatement HaStatusStatement() throws ParseException { + /*@bgen(jjtree) HaStatusStatement */ + OHaStatusStatement jjtn000 = new OHaStatusStatement(JJTHASTATUSSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1));Token token; + try { + jj_consume_token(HA); + jj_consume_token(STATUS); + label_60: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 249: + case 250: + case 251: + case 252: + case 253: + case 254: + ; + break; + default: + jj_la1[362] = jj_gen; + break label_60; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 249: + token = jj_consume_token(249); + jjtn000.servers = true; + break; + case 250: + token = jj_consume_token(250); + jjtn000.db = true; + break; + case 251: + token = jj_consume_token(251); + jjtn000.latency = true; + break; + case 252: + token = jj_consume_token(252); + jjtn000.messages = true; + break; + case 253: + token = jj_consume_token(253); + jjtn000.servers = true; + jjtn000.db = true; + jjtn000.latency = true; + jjtn000.messages = true; + break; + case 254: + token = jj_consume_token(254); + jjtn000.outputText = true; + break; + default: + jj_la1[363] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OHaRemoveServerStatement HaRemoveServerStatement() throws ParseException { + /*@bgen(jjtree) HaRemoveServerStatement */ + OHaRemoveServerStatement jjtn000 = new OHaRemoveServerStatement(JJTHAREMOVESERVERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(HA); + jj_consume_token(REMOVE); + jj_consume_token(SERVER); + jjtn000.serverName = Identifier(); + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OHaSyncDatabaseStatement HaSyncDatabaseStatement() throws ParseException { + /*@bgen(jjtree) HaSyncDatabaseStatement */ + OHaSyncDatabaseStatement jjtn000 = new OHaSyncDatabaseStatement(JJTHASYNCDATABASESTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(HA); + jj_consume_token(SYNC); + jj_consume_token(DATABASE); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 255: + jj_consume_token(255); + jjtn000.force = true; + break; + default: + jj_la1[364] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 256: + jj_consume_token(256); + jjtn000.full = true; + break; + default: + jj_la1[365] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + final public OHaSyncClusterStatement HaSyncClusterStatement() throws ParseException { + /*@bgen(jjtree) HaSyncClusterStatement */ + OHaSyncClusterStatement jjtn000 = new OHaSyncClusterStatement(JJTHASYNCCLUSTERSTATEMENT); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtn000.jjtSetFirstToken(getToken(1)); + try { + jj_consume_token(HA); + jj_consume_token(SYNC); + jj_consume_token(CLUSTER); + jjtn000.clusterName = Identifier(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 257: + case 258: + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 257: + jj_consume_token(257); + jjtn000.modeFull = true; + break; + case 258: + jj_consume_token(258); + jjtn000.modeMerge = true; + break; + default: + jj_la1[366] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[367] = jj_gen; + ; + } + jjtree.closeNodeScope(jjtn000, true); + jjtc000 = false; + jjtn000.jjtSetLastToken(getToken(0)); + {if (true) return jjtn000;} + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtn000.jjtSetLastToken(getToken(0)); + } + } + throw new Error("Missing return statement in function"); + } + + private boolean jj_2_1(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_1(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(0, xla); } + } + + private boolean jj_2_2(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_2(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(1, xla); } + } + + private boolean jj_2_3(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_3(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(2, xla); } + } + + private boolean jj_2_4(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_4(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(3, xla); } + } + + private boolean jj_2_5(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_5(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(4, xla); } + } + + private boolean jj_2_6(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_6(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(5, xla); } + } + + private boolean jj_2_7(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_7(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(6, xla); } + } + + private boolean jj_2_8(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_8(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(7, xla); } + } + + private boolean jj_2_9(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_9(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(8, xla); } + } + + private boolean jj_2_10(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_10(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(9, xla); } + } + + private boolean jj_2_11(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_11(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(10, xla); } + } + + private boolean jj_2_12(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_12(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(11, xla); } + } + + private boolean jj_2_13(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_13(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(12, xla); } + } + + private boolean jj_2_14(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_14(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(13, xla); } + } + + private boolean jj_2_15(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_15(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(14, xla); } + } + + private boolean jj_2_16(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_16(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(15, xla); } + } + + private boolean jj_2_17(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_17(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(16, xla); } + } + + private boolean jj_2_18(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_18(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(17, xla); } + } + + private boolean jj_2_19(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_19(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(18, xla); } + } + + private boolean jj_2_20(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_20(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(19, xla); } + } + + private boolean jj_2_21(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_21(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(20, xla); } + } + + private boolean jj_2_22(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_22(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(21, xla); } + } + + private boolean jj_2_23(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_23(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(22, xla); } + } + + private boolean jj_2_24(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_24(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(23, xla); } + } + + private boolean jj_2_25(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_25(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(24, xla); } + } + + private boolean jj_2_26(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_26(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(25, xla); } + } + + private boolean jj_2_27(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_27(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(26, xla); } + } + + private boolean jj_2_28(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_28(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(27, xla); } + } + + private boolean jj_2_29(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_29(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(28, xla); } + } + + private boolean jj_2_30(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_30(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(29, xla); } + } + + private boolean jj_2_31(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_31(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(30, xla); } + } + + private boolean jj_2_32(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_32(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(31, xla); } + } + + private boolean jj_2_33(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_33(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(32, xla); } + } + + private boolean jj_2_34(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_34(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(33, xla); } + } + + private boolean jj_2_35(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_35(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(34, xla); } + } + + private boolean jj_2_36(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_36(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(35, xla); } + } + + private boolean jj_2_37(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_37(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(36, xla); } + } + + private boolean jj_2_38(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_38(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(37, xla); } + } + + private boolean jj_2_39(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_39(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(38, xla); } + } + + private boolean jj_2_40(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_40(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(39, xla); } + } + + private boolean jj_2_41(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_41(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(40, xla); } + } + + private boolean jj_2_42(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_42(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(41, xla); } + } + + private boolean jj_2_43(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_43(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(42, xla); } + } + + private boolean jj_2_44(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_44(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(43, xla); } + } + + private boolean jj_2_45(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_45(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(44, xla); } + } + + private boolean jj_2_46(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_46(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(45, xla); } + } + + private boolean jj_2_47(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_47(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(46, xla); } + } + + private boolean jj_2_48(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_48(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(47, xla); } + } + + private boolean jj_2_49(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_49(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(48, xla); } + } + + private boolean jj_2_50(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_50(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(49, xla); } + } + + private boolean jj_2_51(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_51(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(50, xla); } + } + + private boolean jj_2_52(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_52(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(51, xla); } + } + + private boolean jj_2_53(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_53(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(52, xla); } + } + + private boolean jj_2_54(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_54(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(53, xla); } + } + + private boolean jj_2_55(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_55(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(54, xla); } + } + + private boolean jj_2_56(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_56(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(55, xla); } + } + + private boolean jj_2_57(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_57(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(56, xla); } + } + + private boolean jj_2_58(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_58(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(57, xla); } + } + + private boolean jj_2_59(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_59(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(58, xla); } + } + + private boolean jj_2_60(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_60(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(59, xla); } + } + + private boolean jj_2_61(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_61(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(60, xla); } + } + + private boolean jj_2_62(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_62(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(61, xla); } + } + + private boolean jj_2_63(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_63(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(62, xla); } + } + + private boolean jj_2_64(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_64(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(63, xla); } + } + + private boolean jj_2_65(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_65(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(64, xla); } + } + + private boolean jj_2_66(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_66(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(65, xla); } + } + + private boolean jj_2_67(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_67(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(66, xla); } + } + + private boolean jj_2_68(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_68(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(67, xla); } + } + + private boolean jj_2_69(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_69(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(68, xla); } + } + + private boolean jj_2_70(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_70(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(69, xla); } + } + + private boolean jj_2_71(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_71(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(70, xla); } + } + + private boolean jj_2_72(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_72(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(71, xla); } + } + + private boolean jj_2_73(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_73(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(72, xla); } + } + + private boolean jj_2_74(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_74(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(73, xla); } + } + + private boolean jj_2_75(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_75(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(74, xla); } + } + + private boolean jj_2_76(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_76(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(75, xla); } + } + + private boolean jj_2_77(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_77(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(76, xla); } + } + + private boolean jj_2_78(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_78(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(77, xla); } + } + + private boolean jj_2_79(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_79(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(78, xla); } + } + + private boolean jj_2_80(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_80(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(79, xla); } + } + + private boolean jj_2_81(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_81(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(80, xla); } + } + + private boolean jj_2_82(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_82(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(81, xla); } + } + + private boolean jj_2_83(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_83(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(82, xla); } + } + + private boolean jj_2_84(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_84(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(83, xla); } + } + + private boolean jj_2_85(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_85(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(84, xla); } + } + + private boolean jj_2_86(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_86(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(85, xla); } + } + + private boolean jj_2_87(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_87(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(86, xla); } + } + + private boolean jj_2_88(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_88(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(87, xla); } + } + + private boolean jj_2_89(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_89(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(88, xla); } + } + + private boolean jj_2_90(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_90(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(89, xla); } + } + + private boolean jj_2_91(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_91(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(90, xla); } + } + + private boolean jj_2_92(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_92(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(91, xla); } + } + + private boolean jj_2_93(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_93(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(92, xla); } + } + + private boolean jj_2_94(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_94(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(93, xla); } + } + + private boolean jj_2_95(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_95(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(94, xla); } + } + + private boolean jj_2_96(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_96(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(95, xla); } + } + + private boolean jj_2_97(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_97(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(96, xla); } + } + + private boolean jj_2_98(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_98(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(97, xla); } + } + + private boolean jj_2_99(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_99(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(98, xla); } + } + + private boolean jj_2_100(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_100(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(99, xla); } + } + + private boolean jj_2_101(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_101(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(100, xla); } + } + + private boolean jj_2_102(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_102(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(101, xla); } + } + + private boolean jj_2_103(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_103(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(102, xla); } + } + + private boolean jj_2_104(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_104(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(103, xla); } + } + + private boolean jj_2_105(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_105(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(104, xla); } + } + + private boolean jj_2_106(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_106(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(105, xla); } + } + + private boolean jj_2_107(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_107(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(106, xla); } + } + + private boolean jj_2_108(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_108(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(107, xla); } + } + + private boolean jj_2_109(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_109(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(108, xla); } + } + + private boolean jj_2_110(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_110(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(109, xla); } + } + + private boolean jj_2_111(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_111(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(110, xla); } + } + + private boolean jj_2_112(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_112(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(111, xla); } + } + + private boolean jj_2_113(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_113(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(112, xla); } + } + + private boolean jj_2_114(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_114(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(113, xla); } + } + + private boolean jj_2_115(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_115(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(114, xla); } + } + + private boolean jj_2_116(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_116(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(115, xla); } + } + + private boolean jj_2_117(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_117(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(116, xla); } + } + + private boolean jj_2_118(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_118(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(117, xla); } + } + + private boolean jj_2_119(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_119(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(118, xla); } + } + + private boolean jj_2_120(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_120(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(119, xla); } + } + + private boolean jj_2_121(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_121(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(120, xla); } + } + + private boolean jj_2_122(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_122(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(121, xla); } + } + + private boolean jj_2_123(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_123(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(122, xla); } + } + + private boolean jj_2_124(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_124(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(123, xla); } + } + + private boolean jj_2_125(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_125(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(124, xla); } + } + + private boolean jj_2_126(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_126(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(125, xla); } + } + + private boolean jj_2_127(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_127(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(126, xla); } + } + + private boolean jj_2_128(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_128(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(127, xla); } + } + + private boolean jj_2_129(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_129(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(128, xla); } + } + + private boolean jj_2_130(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_130(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(129, xla); } + } + + private boolean jj_2_131(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_131(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(130, xla); } + } + + private boolean jj_2_132(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_132(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(131, xla); } + } + + private boolean jj_2_133(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_133(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(132, xla); } + } + + private boolean jj_2_134(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_134(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(133, xla); } + } + + private boolean jj_2_135(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_135(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(134, xla); } + } + + private boolean jj_2_136(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_136(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(135, xla); } + } + + private boolean jj_2_137(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_137(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(136, xla); } + } + + private boolean jj_2_138(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_138(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(137, xla); } + } + + private boolean jj_2_139(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_139(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(138, xla); } + } + + private boolean jj_2_140(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_140(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(139, xla); } + } + + private boolean jj_2_141(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_141(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(140, xla); } + } + + private boolean jj_2_142(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_142(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(141, xla); } + } + + private boolean jj_2_143(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_143(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(142, xla); } + } + + private boolean jj_2_144(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_144(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(143, xla); } + } + + private boolean jj_3R_672() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_779() { + if (jj_scan_token(DESC)) return true; + return false; + } + + private boolean jj_3R_674() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_779()) { + jj_scanpos = xsp; + if (jj_3R_780()) return true; + } + return false; + } + + private boolean jj_3R_581() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_675()) { + jj_scanpos = xsp; + if (jj_3R_676()) { + jj_scanpos = xsp; + if (jj_3R_677()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_678()) jj_scanpos = xsp; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_268() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_671() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_778()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_267() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_266() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_457() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_265() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_456() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_264() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_596() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_73() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(SEQUENCE)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(TYPE)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_740()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_741()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_742()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_263() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_456()) { + jj_scanpos = xsp; + if (jj_3R_457()) return true; + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_580() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_671()) { + jj_scanpos = xsp; + if (jj_3R_672()) { + jj_scanpos = xsp; + if (jj_3R_673()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_674()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_455() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_596()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_650() { + if (jj_scan_token(CONSOLE)) return true; + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_144() { + if (jj_3R_62()) return true; + return false; + } + + private boolean jj_3R_434() { + if (jj_scan_token(ORDER)) return true; + if (jj_scan_token(BY)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_580()) { + jj_scanpos = xsp; + if (jj_3R_581()) return true; + } + while (true) { + xsp = jj_scanpos; + if (jj_3R_582()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_262() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_455()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_261() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_649() { + if (jj_scan_token(SLEEP)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_362() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_841() { + if (jj_3R_651()) return true; + return false; + } + + private boolean jj_3R_361() { + if (jj_scan_token(CHARACTER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_840() { + if (jj_3R_62()) return true; + return false; + } + + private boolean jj_3R_763() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_840()) { + jj_scanpos = xsp; + if (jj_3R_841()) { + jj_scanpos = xsp; + if (jj_scan_token(177)) return true; + } + } + return false; + } + + private boolean jj_3R_360() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3_53() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_103() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(EDGE)) return true; + if (jj_scan_token(TO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_261()) { + jj_scanpos = xsp; + if (jj_3R_262()) { + jj_scanpos = xsp; + if (jj_3R_263()) { + jj_scanpos = xsp; + if (jj_3R_264()) { + jj_scanpos = xsp; + if (jj_3R_265()) return true; + } + } + } + } + xsp = jj_scanpos; + if (jj_3R_266()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_267()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_268()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_52() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3_123() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_152() { + if (jj_3R_116()) return true; + if (jj_scan_token(MATCHES)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_360()) { + jj_scanpos = xsp; + if (jj_3R_361()) { + jj_scanpos = xsp; + if (jj_3R_362()) return true; + } + } + return false; + } + + private boolean jj_3R_276() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_651() { + if (jj_scan_token(IF)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_123()) return true; + if (jj_scan_token(RPAREN)) return true; + if (jj_scan_token(LBRACE)) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_763()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RBRACE)) return true; + return false; + } + + private boolean jj_3R_275() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_762() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_274() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_460() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_273() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_459() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_272() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_359() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_151() { + if (jj_3R_116()) return true; + if (jj_scan_token(CONTAINSTEXT)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_122() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_123()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_597() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_648() { + if (jj_scan_token(RETURN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_762()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_271() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_459()) { + jj_scanpos = xsp; + if (jj_3R_460()) return true; + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_458() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_597()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_647() { + if (jj_scan_token(ROLLBACK)) return true; + return false; + } + + private boolean jj_3R_761() { + if (jj_scan_token(RETRY)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_150() { + if (jj_3R_116()) return true; + if (jj_scan_token(CONTAINSALL)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_122()) { + jj_scanpos = xsp; + if (jj_3R_359()) return true; + } + return false; + } + + private boolean jj_3R_354() { + if (jj_3R_127()) return true; + return false; + } + + private boolean jj_3_121() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_115()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_270() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_458()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_269() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3_120() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_99()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_646() { + if (jj_scan_token(COMMIT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_761()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_760() { + if (jj_scan_token(ISOLATION)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3_143() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_142() { + if (jj_3R_165()) return true; + return false; + } + + private boolean jj_3R_145() { + if (jj_3R_116()) return true; + if (jj_scan_token(NOT)) return true; + if (jj_3R_352()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_120()) { + jj_scanpos = xsp; + if (jj_3_121()) { + jj_scanpos = xsp; + if (jj_3R_354()) return true; + } + } + return false; + } + + private boolean jj_3R_645() { + if (jj_scan_token(BEGIN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_760()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_51() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_653() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_50() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3_119() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_115()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_104() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(EDGE)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(TO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_269()) { + jj_scanpos = xsp; + if (jj_3R_270()) { + jj_scanpos = xsp; + if (jj_3R_271()) { + jj_scanpos = xsp; + if (jj_3R_272()) { + jj_scanpos = xsp; + if (jj_3R_273()) return true; + } + } + } + } + xsp = jj_scanpos; + if (jj_3R_274()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_275()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_276()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_353() { + if (jj_3R_127()) return true; + return false; + } + + private boolean jj_3R_652() { + if (jj_3R_167()) return true; + return false; + } + + private boolean jj_3_118() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_99()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_595() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_454() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_594() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_453() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_260() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_259() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_258() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_685() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_557() { + if (jj_scan_token(LET)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_652()) { + jj_scanpos = xsp; + if (jj_3R_653()) return true; + } + return false; + } + + private boolean jj_3_117() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_828() { + if (jj_scan_token(FALSE)) return true; + return false; + } + + private boolean jj_3R_739() { + if (jj_scan_token(LANGUAGE)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_144() { + if (jj_3R_116()) return true; + if (jj_3R_352()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_118()) { + jj_scanpos = xsp; + if (jj_3_119()) { + jj_scanpos = xsp; + if (jj_3R_353()) return true; + } + } + return false; + } + + private boolean jj_3R_827() { + if (jj_scan_token(TRUE)) return true; + return false; + } + + private boolean jj_3R_452() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_594()) { + jj_scanpos = xsp; + if (jj_3R_595()) return true; + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_593() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_685()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_352() { + if (jj_scan_token(IN)) return true; + return false; + } + + private boolean jj_3R_738() { + if (jj_scan_token(IDEMPOTENT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_827()) { + jj_scanpos = xsp; + if (jj_3R_828()) return true; + } + return false; + } + + private boolean jj_3R_356() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_826() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3_116() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_123()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_451() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_593()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_450() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3_49() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3_48() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_737() { + if (jj_scan_token(PARAMETERS)) return true; + if (jj_scan_token(LBRACKET)) return true; + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_826()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_148() { + if (jj_3R_116()) return true; + if (jj_scan_token(CONTAINS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_116()) { + jj_scanpos = xsp; + if (jj_3R_356()) return true; + } + return false; + } + + private boolean jj_3R_257() { + if (jj_scan_token(TO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_450()) { + jj_scanpos = xsp; + if (jj_3R_451()) { + jj_scanpos = xsp; + if (jj_3R_452()) { + jj_scanpos = xsp; + if (jj_3R_453()) { + jj_scanpos = xsp; + if (jj_3R_454()) return true; + } + } + } + } + return false; + } + + private boolean jj_3R_449() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_256() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_448() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_255() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_142() { + if (jj_3R_116()) return true; + if (jj_scan_token(IS)) return true; + if (jj_scan_token(NOT)) return true; + if (jj_scan_token(DEFINED)) return true; + return false; + } + + private boolean jj_3R_592() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_72() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(FUNCTION)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(STRING_LITERAL)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_737()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_738()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_739()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_254() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_448()) { + jj_scanpos = xsp; + if (jj_3R_449()) return true; + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_143() { + if (jj_3R_116()) return true; + if (jj_scan_token(IS)) return true; + if (jj_scan_token(DEFINED)) return true; + return false; + } + + private boolean jj_3R_759() { + if (jj_scan_token(DOT)) return true; + if (jj_3R_757()) return true; + return false; + } + + private boolean jj_3R_447() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_592()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_140() { + if (jj_3R_116()) return true; + if (jj_scan_token(IS)) return true; + if (jj_scan_token(NOT)) return true; + if (jj_scan_token(NULL)) return true; + return false; + } + + private boolean jj_3R_141() { + if (jj_3R_116()) return true; + if (jj_scan_token(IS)) return true; + if (jj_scan_token(NULL)) return true; + return false; + } + + private boolean jj_3R_253() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_447()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_644() { + if (jj_scan_token(REVOKE)) return true; + if (jj_3R_756()) return true; + if (jj_scan_token(ON)) return true; + if (jj_3R_757()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_759()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(FROM)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_252() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_637() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_758() { + if (jj_scan_token(DOT)) return true; + if (jj_3R_757()) return true; + return false; + } + + private boolean jj_3R_251() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_516() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_637()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_636() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_147() { + if (jj_3R_116()) return true; + if (jj_scan_token(BETWEEN)) return true; + if (jj_3R_116()) return true; + if (jj_scan_token(AND)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_515() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_636()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_643() { + if (jj_scan_token(GRANT)) return true; + if (jj_3R_756()) return true; + if (jj_scan_token(ON)) return true; + if (jj_3R_757()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_758()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(TO)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_591() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_102() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(EDGE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_251()) jj_scanpos = xsp; + if (jj_scan_token(FROM)) return true; + xsp = jj_scanpos; + if (jj_3R_252()) { + jj_scanpos = xsp; + if (jj_3R_253()) { + jj_scanpos = xsp; + if (jj_3R_254()) { + jj_scanpos = xsp; + if (jj_3R_255()) { + jj_scanpos = xsp; + if (jj_3R_256()) return true; + } + } + } + } + xsp = jj_scanpos; + if (jj_3R_257()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_258()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_259()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_260()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_635() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_364() { + if (jj_scan_token(BETWEEN)) return true; + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_515()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + if (jj_scan_token(AND)) return true; + if (jj_scan_token(LBRACKET)) return true; + xsp = jj_scanpos; + if (jj_3R_516()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_250() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_839() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_514() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_635()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_838() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_837() { + if (jj_scan_token(CLUSTER)) return true; + return false; + } + + private boolean jj_3R_446() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_591()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_363() { + if (jj_3R_355()) return true; + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_514()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_757() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_837()) { + jj_scanpos = xsp; + if (jj_3R_838()) { + jj_scanpos = xsp; + if (jj_3R_839()) return true; + } + } + return false; + } + + private boolean jj_3R_249() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_446()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_836() { + if (jj_scan_token(NONE)) return true; + return false; + } + + private boolean jj_3R_248() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_835() { + if (jj_scan_token(ALL)) return true; + return false; + } + + private boolean jj_3R_367() { + if (jj_scan_token(CHARACTER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_834() { + if (jj_scan_token(EXECUTE)) return true; + return false; + } + + private boolean jj_3R_153() { + if (jj_scan_token(KEY)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_363()) { + jj_scanpos = xsp; + if (jj_3R_364()) return true; + } + return false; + } + + private boolean jj_3R_366() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3R_833() { + if (jj_scan_token(DELETE)) return true; + return false; + } + + private boolean jj_3R_365() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_832() { + if (jj_scan_token(UPDATE)) return true; + return false; + } + + private boolean jj_3R_831() { + if (jj_scan_token(READ)) return true; + return false; + } + + private boolean jj_3_47() { + if (jj_3R_105()) return true; + return false; + } + + private boolean jj_3R_830() { + if (jj_scan_token(CREATE)) return true; + return false; + } + + private boolean jj_3_46() { + if (jj_3R_104()) return true; + return false; + } + + private boolean jj_3R_756() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_830()) { + jj_scanpos = xsp; + if (jj_3R_831()) { + jj_scanpos = xsp; + if (jj_3R_832()) { + jj_scanpos = xsp; + if (jj_3R_833()) { + jj_scanpos = xsp; + if (jj_3R_834()) { + jj_scanpos = xsp; + if (jj_3R_835()) { + jj_scanpos = xsp; + if (jj_3R_836()) return true; + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_101() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(EDGE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_248()) { + jj_scanpos = xsp; + if (jj_3R_249()) return true; + } + xsp = jj_scanpos; + if (jj_3R_250()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_45() { + if (jj_3R_103()) return true; + return false; + } + + private boolean jj_3_115() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_44() { + if (jj_3R_102()) return true; + return false; + } + + private boolean jj_3R_173() { + if (jj_3R_105()) return true; + return false; + } + + private boolean jj_3_43() { + if (jj_3R_101()) return true; + return false; + } + + private boolean jj_3R_154() { + if (jj_3R_116()) return true; + if (jj_scan_token(INSTANCEOF)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_365()) { + jj_scanpos = xsp; + if (jj_3R_366()) { + jj_scanpos = xsp; + if (jj_3R_367()) return true; + } + } + return false; + } + + private boolean jj_3R_172() { + if (jj_3R_104()) return true; + return false; + } + + private boolean jj_3R_556() { + if (jj_scan_token(EXPLAIN)) return true; + if (jj_3R_167()) return true; + return false; + } + + private boolean jj_3R_171() { + if (jj_3R_103()) return true; + return false; + } + + private boolean jj_3R_736() { + if (jj_scan_token(INVERSE)) return true; + return false; + } + + private boolean jj_3R_735() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3R_170() { + if (jj_3R_102()) return true; + return false; + } + + private boolean jj_3R_358() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_734() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_169() { + if (jj_3R_101()) return true; + return false; + } + + private boolean jj_3_114() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_123()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_733() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3R_66() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_169()) { + jj_scanpos = xsp; + if (jj_3R_170()) { + jj_scanpos = xsp; + if (jj_3R_171()) { + jj_scanpos = xsp; + if (jj_3R_172()) { + jj_scanpos = xsp; + if (jj_3R_173()) return true; + } + } + } + } + return false; + } + + private boolean jj_3R_732() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_184() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_183() { + if (jj_3R_392()) return true; + return false; + } + + private boolean jj_3R_149() { + if (jj_3R_116()) return true; + if (jj_3R_357()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_114()) { + jj_scanpos = xsp; + if (jj_3R_358()) return true; + } + return false; + } + + private boolean jj_3R_182() { + if (jj_scan_token(CLASS)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_181() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3R_703() { + if (jj_scan_token(EQEQ)) return true; + return false; + } + + private boolean jj_3R_146() { + if (jj_3R_116()) return true; + if (jj_3R_355()) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_702() { + if (jj_scan_token(EQ)) return true; + return false; + } + + private boolean jj_3R_71() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(LINK)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(TYPE)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(FROM)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(DOT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_732()) { + jj_scanpos = xsp; + if (jj_3R_733()) return true; + } + if (jj_scan_token(TO)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(DOT)) return true; + xsp = jj_scanpos; + if (jj_3R_734()) { + jj_scanpos = xsp; + if (jj_3R_735()) return true; + } + xsp = jj_scanpos; + if (jj_3R_736()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_755() { + if (jj_3R_829()) return true; + return false; + } + + private boolean jj_3R_78() { + if (jj_scan_token(MOVE)) return true; + if (jj_scan_token(VERTEX)) return true; + if (jj_3R_180()) return true; + if (jj_scan_token(TO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_181()) { + jj_scanpos = xsp; + if (jj_3R_182()) return true; + } + xsp = jj_scanpos; + if (jj_3R_183()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_184()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_718() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_717() { + if (jj_scan_token(RETURN)) return true; + if (jj_scan_token(BEFORE)) return true; + return false; + } + + private boolean jj_3R_720() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_719() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_716() { + if (jj_scan_token(FROM)) return true; + return false; + } + + private boolean jj_3R_622() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_702()) { + jj_scanpos = xsp; + if (jj_3R_703()) return true; + } + return false; + } + + private boolean jj_3R_642() { + if (jj_scan_token(OPTIMIZE)) return true; + if (jj_scan_token(DATABASE)) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_755()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_357() { + if (jj_scan_token(CONTAINSVALUE)) return true; + return false; + } + + private boolean jj_3R_715() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_714() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_829() { + if (jj_scan_token(MINUS)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_713() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_712() { + if (jj_scan_token(RETURN)) return true; + if (jj_scan_token(BEFORE)) return true; + return false; + } + + private boolean jj_3R_65() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(VERTEX)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_716()) jj_scanpos = xsp; + if (jj_3R_190()) return true; + xsp = jj_scanpos; + if (jj_3R_717()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_718()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_719()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_720()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_630() { + if (jj_scan_token(CONTAINSKEY)) return true; + return false; + } + + private boolean jj_3R_754() { + if (jj_3R_111()) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_810() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_634() { + if (jj_scan_token(SC_AND)) return true; + return false; + } + + private boolean jj_3R_863() { + if (jj_scan_token(AS)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_64() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(FROM)) return true; + if (jj_3R_190()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_712()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_713()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_714()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_715()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_141() { + if (jj_scan_token(CUSTOM)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_753() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_94() { + if (jj_scan_token(ALTER)) return true; + if (jj_scan_token(DATABASE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_141()) { + jj_scanpos = xsp; + if (jj_3R_754()) return true; + } + return false; + } + + private boolean jj_3R_809() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_863()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_752() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_633() { + if (jj_scan_token(WITHIN)) return true; + return false; + } + + private boolean jj_3R_808() { + if (jj_scan_token(AS)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_632() { + if (jj_scan_token(NEAR)) return true; + return false; + } + + private boolean jj_3R_807() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_621()) return true; + return false; + } + + private boolean jj_3R_93() { + if (jj_scan_token(DROP)) return true; + if (jj_scan_token(CLUSTER)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_752()) { + jj_scanpos = xsp; + if (jj_3R_753()) return true; + } + return false; + } + + private boolean jj_3R_232() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_631() { + if (jj_scan_token(LUCENE)) return true; + return false; + } + + private boolean jj_3R_500() { + if (jj_scan_token(MATCH)) return true; + if (jj_3R_621()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_807()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RETURN)) return true; + if (jj_3R_116()) return true; + xsp = jj_scanpos; + if (jj_3R_808()) jj_scanpos = xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_809()) { jj_scanpos = xsp; break; } + } + xsp = jj_scanpos; + if (jj_3R_810()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_92() { + if (jj_scan_token(ALTER)) return true; + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_232()) jj_scanpos = xsp; + if (jj_3R_111()) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_861() { + if (jj_scan_token(BREADTH_FIRST)) return true; + return false; + } + + private boolean jj_3R_860() { + if (jj_scan_token(DEPTH_FIRST)) return true; + return false; + } + + private boolean jj_3R_629() { + if (jj_scan_token(LIKE)) return true; + return false; + } + + private boolean jj_3R_731() { + if (jj_scan_token(ID)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_174() { + if (jj_scan_token(BLOB)) return true; + if (jj_scan_token(CLUSTER)) return true; + return false; + } + + private boolean jj_3R_806() { + if (jj_scan_token(STRATEGY)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_860()) { + jj_scanpos = xsp; + if (jj_3R_861()) return true; + } + return false; + } + + private boolean jj_3R_805() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_628() { + if (jj_scan_token(LE)) return true; + return false; + } + + private boolean jj_3R_804() { + if (jj_scan_token(WHILE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_859() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_701()) return true; + return false; + } + + private boolean jj_3R_803() { + if (jj_scan_token(MAXDEPTH)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_620() { + if (jj_3R_701()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_859()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_586() { + if (jj_3R_584()) return true; + return false; + } + + private boolean jj_3R_627() { + if (jj_scan_token(GE)) return true; + return false; + } + + private boolean jj_3R_70() { + if (jj_scan_token(CREATE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(234)) { + jj_scanpos = xsp; + if (jj_3R_174()) return true; + } + if (jj_3R_111()) return true; + xsp = jj_scanpos; + if (jj_3R_731()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_751() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_585() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_750() { + if (jj_3R_469()) return true; + return false; + } + + private boolean jj_3R_626() { + if (jj_scan_token(NEQ)) return true; + return false; + } + + private boolean jj_3R_499() { + if (jj_scan_token(TRAVERSE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_620()) jj_scanpos = xsp; + if (jj_scan_token(FROM)) return true; + if (jj_3R_190()) return true; + xsp = jj_scanpos; + if (jj_3R_803()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_804()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_805()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_806()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_442() { + if (jj_scan_token(DEFAULT_)) return true; + return false; + } + + private boolean jj_3R_441() { + if (jj_scan_token(SHARED)) return true; + return false; + } + + private boolean jj_3R_244() { + if (jj_scan_token(NOCACHE)) return true; + return false; + } + + private boolean jj_3R_243() { + if (jj_scan_token(PARALLEL)) return true; + return false; + } + + private boolean jj_3R_440() { + if (jj_scan_token(NONE)) return true; + return false; + } + + private boolean jj_3R_91() { + if (jj_scan_token(DROP)) return true; + if (jj_scan_token(INDEX)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_750()) { + jj_scanpos = xsp; + if (jj_3R_751()) return true; + } + return false; + } + + private boolean jj_3R_749() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_625() { + if (jj_scan_token(NE)) return true; + return false; + } + + private boolean jj_3R_439() { + if (jj_scan_token(RECORD)) return true; + return false; + } + + private boolean jj_3R_748() { + if (jj_3R_469()) return true; + return false; + } + + private boolean jj_3R_882() { + if (jj_scan_token(METADATA)) return true; + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_881() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_624() { + if (jj_scan_token(GT)) return true; + return false; + } + + private boolean jj_3R_600() { + if (jj_3R_584()) return true; + return false; + } + + private boolean jj_3R_437() { + if (jj_3R_405()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_586()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_242() { + if (jj_scan_token(LOCK)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_439()) { + jj_scanpos = xsp; + if (jj_3R_440()) { + jj_scanpos = xsp; + if (jj_3R_441()) { + jj_scanpos = xsp; + if (jj_3R_442()) return true; + } + } + } + return false; + } + + private boolean jj_3R_436() { + if (jj_3R_584()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_585()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_239() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_436()) { + jj_scanpos = xsp; + if (jj_3R_437()) return true; + } + return false; + } + + private boolean jj_3R_241() { + if (jj_3R_406()) return true; + return false; + } + + private boolean jj_3R_240() { + if (jj_3R_438()) return true; + return false; + } + + private boolean jj_3R_599() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_641() { + if (jj_scan_token(REBUILD)) return true; + if (jj_scan_token(INDEX)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_748()) { + jj_scanpos = xsp; + if (jj_3R_749()) return true; + } + return false; + } + + private boolean jj_3R_623() { + if (jj_scan_token(LT)) return true; + return false; + } + + private boolean jj_3R_513() { + if (jj_3R_634()) return true; + return false; + } + + private boolean jj_3R_872() { + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_881()) { jj_scanpos = xsp; break; } + } + xsp = jj_scanpos; + if (jj_3R_882()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_512() { + if (jj_3R_633()) return true; + return false; + } + + private boolean jj_3R_511() { + if (jj_3R_632()) return true; + return false; + } + + private boolean jj_3R_238() { + if (jj_3R_435()) return true; + return false; + } + + private boolean jj_3R_510() { + if (jj_3R_631()) return true; + return false; + } + + private boolean jj_3R_237() { + if (jj_3R_434()) return true; + return false; + } + + private boolean jj_3R_509() { + if (jj_3R_630()) return true; + return false; + } + + private boolean jj_3R_501() { + if (jj_3R_622()) return true; + return false; + } + + private boolean jj_3R_236() { + if (jj_3R_433()) return true; + return false; + } + + private boolean jj_3R_508() { + if (jj_3R_629()) return true; + return false; + } + + private boolean jj_3R_235() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_825() { + Token xsp; + xsp = jj_scanpos; + if (jj_3_139()) { + jj_scanpos = xsp; + if (jj_3R_872()) return true; + } + return false; + } + + private boolean jj_3R_507() { + if (jj_3R_628()) return true; + return false; + } + + private boolean jj_3R_234() { + if (jj_3R_411()) return true; + return false; + } + + private boolean jj_3_139() { + if (jj_scan_token(METADATA)) return true; + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_880() { + if (jj_scan_token(METADATA)) return true; + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_879() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_506() { + if (jj_3R_627()) return true; + return false; + } + + private boolean jj_3R_505() { + if (jj_3R_626()) return true; + return false; + } + + private boolean jj_3R_504() { + if (jj_3R_625()) return true; + return false; + } + + private boolean jj_3R_233() { + if (jj_3R_281()) return true; + return false; + } + + private boolean jj_3R_503() { + if (jj_3R_624()) return true; + return false; + } + + private boolean jj_3R_502() { + if (jj_3R_623()) return true; + return false; + } + + private boolean jj_3R_730() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_825()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_464() { + if (jj_3R_405()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_600()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_99() { + if (jj_scan_token(SELECT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_233()) jj_scanpos = xsp; + if (jj_scan_token(FROM)) return true; + if (jj_3R_190()) return true; + xsp = jj_scanpos; + if (jj_3R_234()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_235()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_236()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_237()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_238()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_239()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_240()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_241()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_242()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_243()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_244()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_112() { + if (jj_3R_153()) return true; + return false; + } + + private boolean jj_3R_871() { + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_879()) { jj_scanpos = xsp; break; } + } + xsp = jj_scanpos; + if (jj_3R_880()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_113() { + if (jj_3R_154()) return true; + return false; + } + + private boolean jj_3R_463() { + if (jj_3R_584()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_599()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_284() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_463()) { + jj_scanpos = xsp; + if (jj_3R_464()) return true; + } + return false; + } + + private boolean jj_3R_468() { + if (jj_scan_token(DEFAULT_)) return true; + return false; + } + + private boolean jj_3_111() { + if (jj_3R_152()) return true; + return false; + } + + private boolean jj_3R_355() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_501()) { + jj_scanpos = xsp; + if (jj_3R_502()) { + jj_scanpos = xsp; + if (jj_3R_503()) { + jj_scanpos = xsp; + if (jj_3R_504()) { + jj_scanpos = xsp; + if (jj_3R_505()) { + jj_scanpos = xsp; + if (jj_3R_506()) { + jj_scanpos = xsp; + if (jj_3R_507()) { + jj_scanpos = xsp; + if (jj_3R_508()) { + jj_scanpos = xsp; + if (jj_3R_509()) { + jj_scanpos = xsp; + if (jj_3R_510()) { + jj_scanpos = xsp; + if (jj_3R_511()) { + jj_scanpos = xsp; + if (jj_3R_512()) { + jj_scanpos = xsp; + if (jj_3R_513()) return true; + } + } + } + } + } + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_467() { + if (jj_scan_token(SHARED)) return true; + return false; + } + + private boolean jj_3R_289() { + if (jj_scan_token(NOCACHE)) return true; + return false; + } + + private boolean jj_3R_824() { + Token xsp; + xsp = jj_scanpos; + if (jj_3_138()) { + jj_scanpos = xsp; + if (jj_3R_871()) return true; + } + return false; + } + + private boolean jj_3R_288() { + if (jj_scan_token(PARALLEL)) return true; + return false; + } + + private boolean jj_3_138() { + if (jj_scan_token(METADATA)) return true; + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_466() { + if (jj_scan_token(NONE)) return true; + return false; + } + + private boolean jj_3R_351() { + if (jj_scan_token(FALSE)) return true; + return false; + } + + private boolean jj_3R_465() { + if (jj_scan_token(RECORD)) return true; + return false; + } + + private boolean jj_3_109() { + if (jj_3R_150()) return true; + return false; + } + + private boolean jj_3R_350() { + if (jj_scan_token(TRUE)) return true; + return false; + } + + private boolean jj_3_110() { + if (jj_3R_151()) return true; + return false; + } + + private boolean jj_3R_348() { + if (jj_3R_153()) return true; + return false; + } + + private boolean jj_3_108() { + if (jj_3R_149()) return true; + return false; + } + + private boolean jj_3R_349() { + if (jj_3R_154()) return true; + return false; + } + + private boolean jj_3R_347() { + if (jj_3R_152()) return true; + return false; + } + + private boolean jj_3_107() { + if (jj_3R_148()) return true; + return false; + } + + private boolean jj_3R_287() { + if (jj_scan_token(LOCK)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_465()) { + jj_scanpos = xsp; + if (jj_3R_466()) { + jj_scanpos = xsp; + if (jj_3R_467()) { + jj_scanpos = xsp; + if (jj_3R_468()) return true; + } + } + } + return false; + } + + private boolean jj_3R_286() { + if (jj_3R_406()) return true; + return false; + } + + private boolean jj_3_106() { + if (jj_3R_147()) return true; + return false; + } + + private boolean jj_3_140() { + if (jj_scan_token(ENGINE)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_824()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_285() { + if (jj_3R_438()) return true; + return false; + } + + private boolean jj_3R_878() { + if (jj_scan_token(VALUE)) return true; + return false; + } + + private boolean jj_3R_870() { + if (jj_scan_token(COLLATE)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_345() { + if (jj_3R_150()) return true; + return false; + } + + private boolean jj_3_105() { + if (jj_3R_146()) return true; + return false; + } + + private boolean jj_3R_346() { + if (jj_3R_151()) return true; + return false; + } + + private boolean jj_3R_877() { + if (jj_scan_token(KEY)) return true; + return false; + } + + private boolean jj_3R_729() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_344() { + if (jj_3R_149()) return true; + return false; + } + + private boolean jj_3R_343() { + if (jj_3R_148()) return true; + return false; + } + + private boolean jj_3R_283() { + if (jj_3R_435()) return true; + return false; + } + + private boolean jj_3_103() { + if (jj_3R_144()) return true; + return false; + } + + private boolean jj_3R_869() { + if (jj_scan_token(BY)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_877()) { + jj_scanpos = xsp; + if (jj_3R_878()) return true; + } + return false; + } + + private boolean jj_3R_282() { + if (jj_3R_411()) return true; + return false; + } + + private boolean jj_3_104() { + if (jj_3R_145()) return true; + return false; + } + + private boolean jj_3R_342() { + if (jj_3R_147()) return true; + return false; + } + + private boolean jj_3_42() { + if (jj_3R_100()) return true; + return false; + } + + private boolean jj_3_102() { + if (jj_3R_143()) return true; + return false; + } + + private boolean jj_3R_341() { + if (jj_3R_146()) return true; + return false; + } + + private boolean jj_3_101() { + if (jj_3R_142()) return true; + return false; + } + + private boolean jj_3R_868() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3_100() { + if (jj_3R_141()) return true; + return false; + } + + private boolean jj_3R_106() { + if (jj_scan_token(SELECT)) return true; + if (jj_3R_281()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_282()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_283()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_284()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_285()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_286()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_287()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_288()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_289()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_339() { + if (jj_3R_144()) return true; + return false; + } + + private boolean jj_3_41() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3_99() { + if (jj_3R_140()) return true; + return false; + } + + private boolean jj_3R_340() { + if (jj_3R_145()) return true; + return false; + } + + private boolean jj_3R_867() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_334() { + if (jj_3R_100()) return true; + return false; + } + + private boolean jj_3R_338() { + if (jj_3R_143()) return true; + return false; + } + + private boolean jj_3R_333() { + if (jj_3R_500()) return true; + return false; + } + + private boolean jj_3R_337() { + if (jj_3R_142()) return true; + return false; + } + + private boolean jj_3R_332() { + if (jj_3R_499()) return true; + return false; + } + + private boolean jj_3R_823() { + if (jj_scan_token(COMMA)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_867()) { + jj_scanpos = xsp; + if (jj_3R_868()) return true; + } + xsp = jj_scanpos; + if (jj_3R_869()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_870()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_331() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_336() { + if (jj_3R_141()) return true; + return false; + } + + private boolean jj_3R_330() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_335() { + if (jj_3R_140()) return true; + return false; + } + + private boolean jj_3R_866() { + if (jj_scan_token(VALUE)) return true; + return false; + } + + private boolean jj_3R_822() { + if (jj_scan_token(COLLATE)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_865() { + if (jj_scan_token(KEY)) return true; + return false; + } + + private boolean jj_3R_137() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_330()) { + jj_scanpos = xsp; + if (jj_3R_331()) { + jj_scanpos = xsp; + if (jj_3R_332()) { + jj_scanpos = xsp; + if (jj_3R_333()) { + jj_scanpos = xsp; + if (jj_3R_334()) return true; + } + } + } + } + return false; + } + + private boolean jj_3_98() { + if (jj_3R_139()) return true; + return false; + } + + private boolean jj_3R_821() { + if (jj_scan_token(BY)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_865()) { + jj_scanpos = xsp; + if (jj_3R_866()) return true; + } + return false; + } + + private boolean jj_3R_138() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_335()) { + jj_scanpos = xsp; + if (jj_3R_336()) { + jj_scanpos = xsp; + if (jj_3R_337()) { + jj_scanpos = xsp; + if (jj_3R_338()) { + jj_scanpos = xsp; + if (jj_3R_339()) { + jj_scanpos = xsp; + if (jj_3R_340()) { + jj_scanpos = xsp; + if (jj_3R_341()) { + jj_scanpos = xsp; + if (jj_3R_342()) { + jj_scanpos = xsp; + if (jj_3R_343()) { + jj_scanpos = xsp; + if (jj_3R_344()) { + jj_scanpos = xsp; + if (jj_3R_345()) { + jj_scanpos = xsp; + if (jj_3R_346()) { + jj_scanpos = xsp; + if (jj_3R_347()) { + jj_scanpos = xsp; + if (jj_3R_348()) { + jj_scanpos = xsp; + if (jj_3R_349()) { + jj_scanpos = xsp; + if (jj_3R_350()) { + jj_scanpos = xsp; + if (jj_3R_351()) return true; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3_97() { + if (jj_3R_138()) return true; + return false; + } + + private boolean jj_3_40() { + if (jj_3R_98()) return true; + return false; + } + + private boolean jj_3_96() { + if (jj_3R_139()) return true; + return false; + } + + private boolean jj_3R_382() { + if (jj_3R_557()) return true; + return false; + } + + private boolean jj_3R_139() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_123()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_820() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3_39() { + if (jj_3R_97()) return true; + return false; + } + + private boolean jj_3R_381() { + if (jj_3R_556()) return true; + return false; + } + + private boolean jj_3_95() { + if (jj_3R_138()) return true; + return false; + } + + private boolean jj_3R_697() { + if (jj_3R_139()) return true; + return false; + } + + private boolean jj_3_38() { + if (jj_3R_96()) return true; + return false; + } + + private boolean jj_3R_819() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_696() { + if (jj_3R_138()) return true; + return false; + } + + private boolean jj_3_37() { + if (jj_3R_95()) return true; + return false; + } + + private boolean jj_3R_695() { + if (jj_3R_139()) return true; + return false; + } + + private boolean jj_3R_555() { + if (jj_3R_651()) return true; + return false; + } + + private boolean jj_3R_554() { + if (jj_3R_650()) return true; + return false; + } + + private boolean jj_3R_694() { + if (jj_3R_138()) return true; + return false; + } + + private boolean jj_3R_610() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_696()) { + jj_scanpos = xsp; + if (jj_3R_697()) return true; + } + return false; + } + + private boolean jj_3R_553() { + if (jj_3R_649()) return true; + return false; + } + + private boolean jj_3R_552() { + if (jj_3R_648()) return true; + return false; + } + + private boolean jj_3R_551() { + if (jj_3R_647()) return true; + return false; + } + + private boolean jj_3R_550() { + if (jj_3R_646()) return true; + return false; + } + + private boolean jj_3R_549() { + if (jj_3R_645()) return true; + return false; + } + + private boolean jj_3_137() { + if (jj_scan_token(ON)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_819()) { + jj_scanpos = xsp; + if (jj_3R_820()) return true; + } + xsp = jj_scanpos; + if (jj_3R_821()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_822()) jj_scanpos = xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_823()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_609() { + if (jj_scan_token(NOT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_694()) { + jj_scanpos = xsp; + if (jj_3R_695()) return true; + } + return false; + } + + private boolean jj_3R_548() { + if (jj_3R_644()) return true; + return false; + } + + private boolean jj_3R_547() { + if (jj_3R_643()) return true; + return false; + } + + private boolean jj_3R_69() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(INDEX)) return true; + if (jj_3R_469()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_137()) { + jj_scanpos = xsp; + if (jj_3R_729()) return true; + } + xsp = jj_scanpos; + if (jj_3_140()) { + jj_scanpos = xsp; + if (jj_3R_730()) return true; + } + return false; + } + + private boolean jj_3_34() { + if (jj_3R_92()) return true; + return false; + } + + private boolean jj_3R_546() { + if (jj_3R_642()) return true; + return false; + } + + private boolean jj_3R_483() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_609()) { + jj_scanpos = xsp; + if (jj_3R_610()) return true; + } + return false; + } + + private boolean jj_3R_484() { + if (jj_scan_token(AND)) return true; + if (jj_3R_483()) return true; + return false; + } + + private boolean jj_3_36() { + if (jj_3R_94()) return true; + return false; + } + + private boolean jj_3_35() { + if (jj_3R_93()) return true; + return false; + } + + private boolean jj_3_32() { + if (jj_3R_90()) return true; + return false; + } + + private boolean jj_3R_231() { + if (jj_scan_token(FORCE)) return true; + return false; + } + + private boolean jj_3R_314() { + if (jj_scan_token(OR)) return true; + if (jj_3R_313()) return true; + return false; + } + + private boolean jj_3R_230() { + if (jj_scan_token(IF)) return true; + if (jj_scan_token(EXISTS)) return true; + return false; + } + + private boolean jj_3R_545() { + if (jj_3R_92()) return true; + return false; + } + + private boolean jj_3_31() { + if (jj_3R_89()) return true; + return false; + } + + private boolean jj_3R_313() { + if (jj_3R_483()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_484()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_33() { + if (jj_3R_91()) return true; + return false; + } + + private boolean jj_3_30() { + if (jj_3R_88()) return true; + return false; + } + + private boolean jj_3R_544() { + if (jj_3R_641()) return true; + return false; + } + + private boolean jj_3R_90() { + if (jj_scan_token(DROP)) return true; + if (jj_scan_token(PROPERTY)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_230()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_231()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_123() { + if (jj_3R_313()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_314()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_543() { + if (jj_3R_90()) return true; + return false; + } + + private boolean jj_3R_400() { + if (jj_3R_123()) return true; + return false; + } + + private boolean jj_3_28() { + if (jj_3R_86()) return true; + return false; + } + + private boolean jj_3R_542() { + if (jj_3R_89()) return true; + return false; + } + + private boolean jj_3R_229() { + if (jj_3R_111()) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_541() { + if (jj_3R_88()) return true; + return false; + } + + private boolean jj_3R_472() { + if (jj_scan_token(INDEXVALUESDESC_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3_26() { + if (jj_3R_84()) return true; + return false; + } + + private boolean jj_3R_471() { + if (jj_scan_token(INDEXVALUESASC_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3_29() { + if (jj_3R_87()) return true; + return false; + } + + private boolean jj_3R_470() { + if (jj_scan_token(INDEXVALUES_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3_25() { + if (jj_3R_83()) return true; + return false; + } + + private boolean jj_3R_540() { + if (jj_3R_86()) return true; + return false; + } + + private boolean jj_3_136() { + if (jj_scan_token(CUSTOM)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_24() { + if (jj_3R_82()) return true; + return false; + } + + private boolean jj_3_27() { + if (jj_3R_85()) return true; + return false; + } + + private boolean jj_3R_539() { + if (jj_3R_84()) return true; + return false; + } + + private boolean jj_3_23() { + if (jj_3R_81()) return true; + return false; + } + + private boolean jj_3R_538() { + if (jj_3R_83()) return true; + return false; + } + + private boolean jj_3R_291() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_470()) { + jj_scanpos = xsp; + if (jj_3R_471()) { + jj_scanpos = xsp; + if (jj_3R_472()) return true; + } + } + return false; + } + + private boolean jj_3_22() { + if (jj_3R_80()) return true; + return false; + } + + private boolean jj_3R_537() { + if (jj_3R_82()) return true; + return false; + } + + private boolean jj_3_21() { + if (jj_3R_79()) return true; + return false; + } + + private boolean jj_3R_536() { + if (jj_3R_640()) return true; + return false; + } + + private boolean jj_3R_290() { + if (jj_scan_token(INDEX_COLON)) return true; + if (jj_3R_469()) return true; + return false; + } + + private boolean jj_3_20() { + if (jj_3R_78()) return true; + return false; + } + + private boolean jj_3R_535() { + if (jj_3R_81()) return true; + return false; + } + + private boolean jj_3R_89() { + if (jj_scan_token(ALTER)) return true; + if (jj_scan_token(PROPERTY)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_136()) { + jj_scanpos = xsp; + if (jj_3R_229()) return true; + } + return false; + } + + private boolean jj_3_19() { + if (jj_3R_77()) return true; + return false; + } + + private boolean jj_3R_107() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_290()) { + jj_scanpos = xsp; + if (jj_3R_291()) return true; + } + return false; + } + + private boolean jj_3R_534() { + if (jj_3R_80()) return true; + return false; + } + + private boolean jj_3_18() { + if (jj_3R_76()) return true; + return false; + } + + private boolean jj_3R_533() { + if (jj_3R_79()) return true; + return false; + } + + private boolean jj_3R_864() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_17() { + if (jj_3R_75()) return true; + return false; + } + + private boolean jj_3R_532() { + if (jj_3R_78()) return true; + return false; + } + + private boolean jj_3R_688() { + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3_16() { + if (jj_3R_74()) return true; + return false; + } + + private boolean jj_3R_531() { + if (jj_3R_77()) return true; + return false; + } + + private boolean jj_3R_687() { + if (jj_scan_token(DOT)) return true; + return false; + } + + private boolean jj_3R_818() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_817()) return true; + return false; + } + + private boolean jj_3R_728() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_530() { + if (jj_3R_76()) return true; + return false; + } + + private boolean jj_3R_726() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_727() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_817()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_818()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_602() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_687()) { + jj_scanpos = xsp; + if (jj_3R_688()) return true; + } + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_529() { + if (jj_3R_75()) return true; + return false; + } + + private boolean jj_3R_817() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_864()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_528() { + if (jj_3R_74()) return true; + return false; + } + + private boolean jj_3R_601() { + if (jj_scan_token(247)) return true; + return false; + } + + private boolean jj_3_135() { + if (jj_3R_163()) return true; + return false; + } + + private boolean jj_3_15() { + if (jj_3R_73()) return true; + return false; + } + + private boolean jj_3R_469() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_601()) jj_scanpos = xsp; + if (jj_3R_111()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_602()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_14() { + if (jj_3R_72()) return true; + return false; + } + + private boolean jj_3_13() { + if (jj_3R_71()) return true; + return false; + } + + private boolean jj_3_12() { + if (jj_3R_70()) return true; + return false; + } + + private boolean jj_3R_768() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3_11() { + if (jj_3R_69()) return true; + return false; + } + + private boolean jj_3R_654() { + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_768()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_10() { + if (jj_3R_68()) return true; + return false; + } + + private boolean jj_3R_559() { + if (jj_scan_token(METADATA_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3_9() { + if (jj_3R_67()) return true; + return false; + } + + private boolean jj_3R_68() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(PROPERTY)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_135()) jj_scanpos = xsp; + if (jj_3R_111()) return true; + xsp = jj_scanpos; + if (jj_3R_726()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_727()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_728()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_527() { + if (jj_3R_492()) return true; + return false; + } + + private boolean jj_3_8() { + if (jj_3R_66()) return true; + return false; + } + + private boolean jj_3R_432() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3_7() { + if (jj_3R_65()) return true; + return false; + } + + private boolean jj_3_6() { + if (jj_3R_64()) return true; + return false; + } + + private boolean jj_3R_558() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_654()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3_93() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_163() { + if (jj_scan_token(IF)) return true; + if (jj_scan_token(NOT)) return true; + if (jj_scan_token(EXISTS)) return true; + return false; + } + + private boolean jj_3R_228() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_526() { + if (jj_3R_137()) return true; + return false; + } + + private boolean jj_3R_294() { + if (jj_scan_token(CLUSTER_NUMBER_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3R_227() { + if (jj_scan_token(IF)) return true; + if (jj_scan_token(EXISTS)) return true; + return false; + } + + private boolean jj_3R_293() { + if (jj_scan_token(CLUSTER_IDENTIFIER)) return true; + return false; + } + + private boolean jj_3R_88() { + if (jj_scan_token(DROP)) return true; + if (jj_scan_token(CLASS)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_227()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_228()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_431() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3R_560() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_430() { + if (jj_scan_token(248)) return true; + return false; + } + + private boolean jj_3R_380() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_526()) { + jj_scanpos = xsp; + if (jj_3_6()) { + jj_scanpos = xsp; + if (jj_3_7()) { + jj_scanpos = xsp; + if (jj_3_8()) { + jj_scanpos = xsp; + if (jj_3R_527()) { + jj_scanpos = xsp; + if (jj_3_9()) { + jj_scanpos = xsp; + if (jj_3_10()) { + jj_scanpos = xsp; + if (jj_3_11()) { + jj_scanpos = xsp; + if (jj_3_12()) { + jj_scanpos = xsp; + if (jj_3_13()) { + jj_scanpos = xsp; + if (jj_3_14()) { + jj_scanpos = xsp; + if (jj_3_15()) { + jj_scanpos = xsp; + if (jj_3R_528()) { + jj_scanpos = xsp; + if (jj_3R_529()) { + jj_scanpos = xsp; + if (jj_3R_530()) { + jj_scanpos = xsp; + if (jj_3R_531()) { + jj_scanpos = xsp; + if (jj_3R_532()) { + jj_scanpos = xsp; + if (jj_3R_533()) { + jj_scanpos = xsp; + if (jj_3R_534()) { + jj_scanpos = xsp; + if (jj_3R_535()) { + jj_scanpos = xsp; + if (jj_3R_536()) { + jj_scanpos = xsp; + if (jj_3R_537()) { + jj_scanpos = xsp; + if (jj_3R_538()) { + jj_scanpos = xsp; + if (jj_3R_539()) { + jj_scanpos = xsp; + if (jj_3_27()) { + jj_scanpos = xsp; + if (jj_3R_540()) { + jj_scanpos = xsp; + if (jj_3_29()) { + jj_scanpos = xsp; + if (jj_3R_541()) { + jj_scanpos = xsp; + if (jj_3R_542()) { + jj_scanpos = xsp; + if (jj_3R_543()) { + jj_scanpos = xsp; + if (jj_3R_544()) { + jj_scanpos = xsp; + if (jj_3_33()) { + jj_scanpos = xsp; + if (jj_3R_545()) { + jj_scanpos = xsp; + if (jj_3_35()) { + jj_scanpos = xsp; + if (jj_3_36()) { + jj_scanpos = xsp; + if (jj_3R_546()) { + jj_scanpos = xsp; + if (jj_3R_547()) { + jj_scanpos = xsp; + if (jj_3R_548()) { + jj_scanpos = xsp; + if (jj_3R_549()) { + jj_scanpos = xsp; + if (jj_3R_550()) { + jj_scanpos = xsp; + if (jj_3R_551()) { + jj_scanpos = xsp; + if (jj_3R_552()) { + jj_scanpos = xsp; + if (jj_3R_553()) { + jj_scanpos = xsp; + if (jj_3R_554()) { + jj_scanpos = xsp; + if (jj_3R_555()) { + jj_scanpos = xsp; + if (jj_3_37()) { + jj_scanpos = xsp; + if (jj_3_38()) { + jj_scanpos = xsp; + if (jj_3_39()) { + jj_scanpos = xsp; + if (jj_3_40()) return true; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_226() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_113() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_293()) { + jj_scanpos = xsp; + if (jj_3R_294()) return true; + } + return false; + } + + private boolean jj_3R_429() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_225() { + if (jj_scan_token(ENCRYPTION)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_432()) { + jj_scanpos = xsp; + if (jj_scan_token(37)) return true; + } + return false; + } + + private boolean jj_3R_167() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_380()) { + jj_scanpos = xsp; + if (jj_3R_381()) { + jj_scanpos = xsp; + if (jj_3R_382()) return true; + } + } + return false; + } + + private boolean jj_3_94() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_224() { + if (jj_scan_token(DESCRIPTION)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_391() { + if (jj_3R_493()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_560()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_390() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_62() { + if (jj_3R_167()) return true; + if (jj_scan_token(SEMICOLON)) return true; + return false; + } + + private boolean jj_3R_389() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_137()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_572() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_571()) return true; + return false; + } + + private boolean jj_3_5() { + if (jj_3R_63()) return true; + return false; + } + + private boolean jj_3R_388() { + if (jj_3R_559()) return true; + return false; + } + + private boolean jj_3_4() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_387() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_223() { + if (jj_scan_token(CLUSTERSELECTION)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_429()) { + jj_scanpos = xsp; + if (jj_3R_430()) { + jj_scanpos = xsp; + if (jj_3R_431()) return true; + } + } + return false; + } + + private boolean jj_3R_386() { + if (jj_3R_558()) return true; + return false; + } + + private boolean jj_3R_165() { + if (jj_3R_167()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(177)) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_385() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3R_427() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_426() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_578() { + if (jj_3R_63()) return true; + return false; + } + + private boolean jj_3R_222() { + if (jj_scan_token(ABSTRACT)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_577() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_428() { + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_384() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_425() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_424() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_423() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_577()) { + jj_scanpos = xsp; + if (jj_3R_578()) return true; + } + return false; + } + + private boolean jj_3R_221() { + if (jj_scan_token(CUSTOM)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_428()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_168() { + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_180() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_384()) { + jj_scanpos = xsp; + if (jj_3R_385()) { + jj_scanpos = xsp; + if (jj_3R_386()) { + jj_scanpos = xsp; + if (jj_3R_387()) { + jj_scanpos = xsp; + if (jj_3R_388()) { + jj_scanpos = xsp; + if (jj_3R_389()) { + jj_scanpos = xsp; + if (jj_3R_390()) { + jj_scanpos = xsp; + if (jj_3R_391()) return true; + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_576() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_63() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_168()) jj_scanpos = xsp; + if (jj_scan_token(FLOATING_POINT_LITERAL)) return true; + return false; + } + + private boolean jj_3R_220() { + if (jj_scan_token(REMOVECLUSTER)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_426()) { + jj_scanpos = xsp; + if (jj_3R_427()) return true; + } + return false; + } + + private boolean jj_3_92() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_422() { + if (jj_scan_token(NULL)) return true; + return false; + } + + private boolean jj_3R_166() { + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_219() { + if (jj_scan_token(ADDCLUSTER)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_424()) { + jj_scanpos = xsp; + if (jj_3R_425()) return true; + } + return false; + } + + private boolean jj_3R_670() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_137()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_61() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_166()) jj_scanpos = xsp; + if (jj_scan_token(INTEGER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_218() { + if (jj_scan_token(STRICTMODE)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_421() { + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_576()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_669() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_217() { + if (jj_scan_token(OVERSIZE)) return true; + if (jj_3R_423()) return true; + return false; + } + + private boolean jj_3R_420() { + if (jj_scan_token(NULL)) return true; + return false; + } + + private boolean jj_3R_571() { + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_669()) { + jj_scanpos = xsp; + if (jj_3R_670()) return true; + } + return false; + } + + private boolean jj_3R_419() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_575() { + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_574() { + if (jj_scan_token(PLUS)) return true; + return false; + } + + private boolean jj_3R_418() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_574()) { + jj_scanpos = xsp; + if (jj_3R_575()) return true; + } + return false; + } + + private boolean jj_3R_411() { + if (jj_scan_token(LET)) return true; + if (jj_3R_571()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_572()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_91() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_216() { + if (jj_scan_token(SUPERCLASSES)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_421()) { + jj_scanpos = xsp; + if (jj_3R_422()) return true; + } + return false; + } + + private boolean jj_3R_190() { + if (jj_3R_180()) return true; + return false; + } + + private boolean jj_3R_417() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_498() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3_90() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_497() { + if (jj_scan_token(CHARACTER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_496() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3R_215() { + if (jj_scan_token(SUPERCLASS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_418()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_419()) { + jj_scanpos = xsp; + if (jj_3R_420()) return true; + } + return false; + } + + private boolean jj_3_89() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_495() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_329() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_496()) { + jj_scanpos = xsp; + if (jj_3R_497()) return true; + } + xsp = jj_scanpos; + if (jj_3R_498()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_214() { + if (jj_scan_token(SHORTNAME)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_417()) { + jj_scanpos = xsp; + if (jj_scan_token(37)) return true; + } + return false; + } + + private boolean jj_3R_129() { + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_494() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_213() { + if (jj_scan_token(NAME)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_328() { + if (jj_3R_115()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_495()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_816() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_327() { + if (jj_3R_493()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_494()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_326() { + if (jj_3R_423()) return true; + return false; + } + + private boolean jj_3R_815() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_86() { + if (jj_scan_token(ALTER)) return true; + if (jj_scan_token(CLASS)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_213()) { + jj_scanpos = xsp; + if (jj_3R_214()) { + jj_scanpos = xsp; + if (jj_3R_215()) { + jj_scanpos = xsp; + if (jj_3R_216()) { + jj_scanpos = xsp; + if (jj_3R_217()) { + jj_scanpos = xsp; + if (jj_3R_218()) { + jj_scanpos = xsp; + if (jj_3R_219()) { + jj_scanpos = xsp; + if (jj_3R_220()) { + jj_scanpos = xsp; + if (jj_3R_221()) { + jj_scanpos = xsp; + if (jj_3R_222()) { + jj_scanpos = xsp; + if (jj_3R_223()) { + jj_scanpos = xsp; + if (jj_3R_224()) { + jj_scanpos = xsp; + if (jj_3R_225()) return true; + } + } + } + } + } + } + } + } + } + } + } + } + xsp = jj_scanpos; + if (jj_3R_226()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_725() { + if (jj_scan_token(ABSTRACT)) return true; + return false; + } + + private boolean jj_3R_724() { + if (jj_scan_token(CLUSTERS)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_723() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_61()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_816()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_136() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_326()) { + jj_scanpos = xsp; + if (jj_3R_327()) { + jj_scanpos = xsp; + if (jj_3R_328()) { + jj_scanpos = xsp; + if (jj_3R_329()) return true; + } + } + } + return false; + } + + private boolean jj_3R_324() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_325() { + if (jj_3R_492()) return true; + return false; + } + + private boolean jj_3_88() { + if (jj_3R_137()) return true; + return false; + } + + private boolean jj_3R_722() { + if (jj_scan_token(EXTENDS)) return true; + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_815()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_87() { + if (jj_3R_136()) return true; + return false; + } + + private boolean jj_3R_721() { + if (jj_scan_token(IF)) return true; + if (jj_scan_token(NOT)) return true; + if (jj_scan_token(EXISTS)) return true; + return false; + } + + private boolean jj_3_86() { + if (jj_3R_135()) return true; + return false; + } + + private boolean jj_3R_590() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3R_135() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_88()) { + jj_scanpos = xsp; + if (jj_3R_324()) { + jj_scanpos = xsp; + if (jj_3R_325()) return true; + } + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_589() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_323() { + if (jj_3R_136()) return true; + return false; + } + + private boolean jj_3R_322() { + if (jj_3R_135()) return true; + return false; + } + + private boolean jj_3R_67() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(CLASS)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_721()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_722()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_723()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_724()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_725()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_134() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_322()) { + jj_scanpos = xsp; + if (jj_3R_323()) return true; + } + return false; + } + + private boolean jj_3R_133() { + if (jj_scan_token(REM)) return true; + return false; + } + + private boolean jj_3R_445() { + if (jj_scan_token(COMMA)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_589()) { + jj_scanpos = xsp; + if (jj_3R_590()) return true; + } + return false; + } + + private boolean jj_3_134() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_132() { + if (jj_scan_token(SLASH)) return true; + return false; + } + + private boolean jj_3R_444() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_131() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_573() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3_85() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_131()) { + jj_scanpos = xsp; + if (jj_3R_132()) { + jj_scanpos = xsp; + if (jj_3R_133()) return true; + } + } + if (jj_3R_134()) return true; + return false; + } + + private boolean jj_3R_443() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_128() { + if (jj_scan_token(PLUS)) return true; + return false; + } + + private boolean jj_3R_247() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_443()) { + jj_scanpos = xsp; + if (jj_3R_444()) return true; + } + while (true) { + xsp = jj_scanpos; + if (jj_3R_445()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_416() { + if (jj_3R_114()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_573()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_130() { + if (jj_3R_134()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3_85()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_246() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_167()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_245() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3_84() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_128()) { + jj_scanpos = xsp; + if (jj_3R_129()) return true; + } + if (jj_3R_130()) return true; + return false; + } + + private boolean jj_3R_100() { + if (jj_scan_token(FIND)) return true; + if (jj_scan_token(REFERENCES)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_245()) { + jj_scanpos = xsp; + if (jj_3R_246()) return true; + } + xsp = jj_scanpos; + if (jj_3R_247()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_212() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_416()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_211() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_127() { + if (jj_3R_130()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3_84()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_83() { + if (jj_3R_127()) return true; + return false; + } + + private boolean jj_3_82() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_302() { + if (jj_scan_token(FALSE)) return true; + return false; + } + + private boolean jj_3R_84() { + if (jj_scan_token(TRUNCATE)) return true; + if (jj_scan_token(RECORD)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_211()) { + jj_scanpos = xsp; + if (jj_3R_212()) return true; + } + return false; + } + + private boolean jj_3R_209() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_301() { + if (jj_scan_token(TRUE)) return true; + return false; + } + + private boolean jj_3R_208() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_300() { + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_210() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_299() { + if (jj_3R_127()) return true; + return false; + } + + private boolean jj_3_81() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_298() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3_79() { + if (jj_3R_124()) return true; + return false; + } + + private boolean jj_3R_297() { + if (jj_scan_token(NULL)) return true; + return false; + } + + private boolean jj_3R_83() { + if (jj_scan_token(TRUNCATE)) return true; + if (jj_scan_token(CLUSTER)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_208()) { + jj_scanpos = xsp; + if (jj_3R_209()) return true; + } + xsp = jj_scanpos; + if (jj_3R_210()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_80() { + if (jj_3R_125()) return true; + return false; + } + + private boolean jj_3_78() { + if (jj_3R_123()) return true; + return false; + } + + private boolean jj_3R_116() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_297()) { + jj_scanpos = xsp; + if (jj_3R_298()) { + jj_scanpos = xsp; + if (jj_3R_299()) { + jj_scanpos = xsp; + if (jj_3R_300()) { + jj_scanpos = xsp; + if (jj_3R_301()) { + jj_scanpos = xsp; + if (jj_3R_302()) return true; + } + } + } + } + } + return false; + } + + private boolean jj_3_77() { + if (jj_3R_122()) return true; + return false; + } + + private boolean jj_3R_207() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3R_321() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_206() { + if (jj_scan_token(POLYMORPHIC)) return true; + return false; + } + + private boolean jj_3R_320() { + if (jj_scan_token(DOT)) return true; + if (jj_3R_121()) return true; + return false; + } + + private boolean jj_3R_744() { + if (jj_scan_token(OFF)) return true; + return false; + } + + private boolean jj_3R_491() { + if (jj_3R_124()) return true; + return false; + } + + private boolean jj_3R_82() { + if (jj_scan_token(TRUNCATE)) return true; + if (jj_scan_token(CLASS)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_206()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_207()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_743() { + if (jj_scan_token(ON)) return true; + return false; + } + + private boolean jj_3R_319() { + if (jj_3R_125()) return true; + return false; + } + + private boolean jj_3R_490() { + if (jj_3R_123()) return true; + return false; + } + + private boolean jj_3R_489() { + if (jj_3R_122()) return true; + return false; + } + + private boolean jj_3R_640() { + if (jj_scan_token(PROFILE)) return true; + if (jj_scan_token(STORAGE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_743()) { + jj_scanpos = xsp; + if (jj_3R_744()) return true; + } + return false; + } + + private boolean jj_3R_318() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_489()) { + jj_scanpos = xsp; + if (jj_3R_490()) { + jj_scanpos = xsp; + if (jj_3R_491()) return true; + } + } + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_478() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_76() { + if (jj_3R_121()) return true; + return false; + } + + private boolean jj_3_75() { + if (jj_3R_120()) return true; + return false; + } + + private boolean jj_3R_126() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_318()) { + jj_scanpos = xsp; + if (jj_3R_319()) { + jj_scanpos = xsp; + if (jj_3R_320()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_321()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_525() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_379() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_619() { + if (jj_3R_121()) return true; + return false; + } + + private boolean jj_3R_618() { + if (jj_3R_120()) return true; + return false; + } + + private boolean jj_3R_111() { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(235)) { + jj_scanpos = xsp; + if (jj_scan_token(222)) { + jj_scanpos = xsp; + if (jj_scan_token(28)) { + jj_scanpos = xsp; + if (jj_scan_token(30)) { + jj_scanpos = xsp; + if (jj_scan_token(29)) { + jj_scanpos = xsp; + if (jj_scan_token(33)) { + jj_scanpos = xsp; + if (jj_scan_token(31)) { + jj_scanpos = xsp; + if (jj_scan_token(32)) { + jj_scanpos = xsp; + if (jj_scan_token(39)) { + jj_scanpos = xsp; + if (jj_scan_token(232)) { + jj_scanpos = xsp; + if (jj_scan_token(45)) { + jj_scanpos = xsp; + if (jj_scan_token(40)) { + jj_scanpos = xsp; + if (jj_scan_token(26)) { + jj_scanpos = xsp; + if (jj_scan_token(27)) { + jj_scanpos = xsp; + if (jj_scan_token(55)) { + jj_scanpos = xsp; + if (jj_scan_token(22)) { + jj_scanpos = xsp; + if (jj_scan_token(66)) { + jj_scanpos = xsp; + if (jj_scan_token(72)) { + jj_scanpos = xsp; + if (jj_scan_token(74)) { + jj_scanpos = xsp; + if (jj_scan_token(71)) { + jj_scanpos = xsp; + if (jj_scan_token(67)) { + jj_scanpos = xsp; + if (jj_scan_token(68)) { + jj_scanpos = xsp; + if (jj_scan_token(76)) { + jj_scanpos = xsp; + if (jj_scan_token(77)) { + jj_scanpos = xsp; + if (jj_scan_token(78)) { + jj_scanpos = xsp; + if (jj_scan_token(79)) { + jj_scanpos = xsp; + if (jj_scan_token(80)) { + jj_scanpos = xsp; + if (jj_scan_token(81)) { + jj_scanpos = xsp; + if (jj_scan_token(83)) { + jj_scanpos = xsp; + if (jj_scan_token(84)) { + jj_scanpos = xsp; + if (jj_scan_token(85)) { + jj_scanpos = xsp; + if (jj_scan_token(86)) { + jj_scanpos = xsp; + if (jj_scan_token(87)) { + jj_scanpos = xsp; + if (jj_scan_token(88)) { + jj_scanpos = xsp; + if (jj_scan_token(89)) { + jj_scanpos = xsp; + if (jj_scan_token(90)) { + jj_scanpos = xsp; + if (jj_scan_token(73)) { + jj_scanpos = xsp; + if (jj_scan_token(75)) { + jj_scanpos = xsp; + if (jj_scan_token(91)) { + jj_scanpos = xsp; + if (jj_scan_token(92)) { + jj_scanpos = xsp; + if (jj_scan_token(93)) { + jj_scanpos = xsp; + if (jj_scan_token(94)) { + jj_scanpos = xsp; + if (jj_scan_token(95)) { + jj_scanpos = xsp; + if (jj_scan_token(96)) { + jj_scanpos = xsp; + if (jj_scan_token(97)) { + jj_scanpos = xsp; + if (jj_scan_token(98)) { + jj_scanpos = xsp; + if (jj_scan_token(99)) { + jj_scanpos = xsp; + if (jj_scan_token(100)) { + jj_scanpos = xsp; + if (jj_scan_token(101)) { + jj_scanpos = xsp; + if (jj_scan_token(102)) { + jj_scanpos = xsp; + if (jj_scan_token(104)) { + jj_scanpos = xsp; + if (jj_scan_token(103)) { + jj_scanpos = xsp; + if (jj_scan_token(105)) { + jj_scanpos = xsp; + if (jj_scan_token(106)) { + jj_scanpos = xsp; + if (jj_scan_token(107)) { + jj_scanpos = xsp; + if (jj_scan_token(108)) { + jj_scanpos = xsp; + if (jj_scan_token(109)) { + jj_scanpos = xsp; + if (jj_scan_token(110)) { + jj_scanpos = xsp; + if (jj_scan_token(111)) { + jj_scanpos = xsp; + if (jj_scan_token(112)) { + jj_scanpos = xsp; + if (jj_scan_token(113)) { + jj_scanpos = xsp; + if (jj_scan_token(114)) { + jj_scanpos = xsp; + if (jj_scan_token(115)) { + jj_scanpos = xsp; + if (jj_scan_token(116)) { + jj_scanpos = xsp; + if (jj_scan_token(117)) { + jj_scanpos = xsp; + if (jj_scan_token(118)) { + jj_scanpos = xsp; + if (jj_scan_token(119)) { + jj_scanpos = xsp; + if (jj_scan_token(120)) { + jj_scanpos = xsp; + if (jj_scan_token(121)) { + jj_scanpos = xsp; + if (jj_scan_token(122)) { + jj_scanpos = xsp; + if (jj_scan_token(123)) { + jj_scanpos = xsp; + if (jj_scan_token(124)) { + jj_scanpos = xsp; + if (jj_scan_token(125)) { + jj_scanpos = xsp; + if (jj_scan_token(126)) { + jj_scanpos = xsp; + if (jj_scan_token(127)) { + jj_scanpos = xsp; + if (jj_scan_token(128)) { + jj_scanpos = xsp; + if (jj_scan_token(129)) { + jj_scanpos = xsp; + if (jj_scan_token(130)) { + jj_scanpos = xsp; + if (jj_scan_token(131)) { + jj_scanpos = xsp; + if (jj_scan_token(132)) { + jj_scanpos = xsp; + if (jj_scan_token(133)) { + jj_scanpos = xsp; + if (jj_scan_token(134)) { + jj_scanpos = xsp; + if (jj_scan_token(59)) { + jj_scanpos = xsp; + if (jj_scan_token(135)) { + jj_scanpos = xsp; + if (jj_scan_token(136)) { + jj_scanpos = xsp; + if (jj_scan_token(137)) { + jj_scanpos = xsp; + if (jj_scan_token(138)) { + jj_scanpos = xsp; + if (jj_scan_token(139)) { + jj_scanpos = xsp; + if (jj_scan_token(140)) { + jj_scanpos = xsp; + if (jj_scan_token(141)) { + jj_scanpos = xsp; + if (jj_scan_token(142)) { + jj_scanpos = xsp; + if (jj_scan_token(143)) { + jj_scanpos = xsp; + if (jj_scan_token(144)) { + jj_scanpos = xsp; + if (jj_scan_token(145)) { + jj_scanpos = xsp; + if (jj_scan_token(236)) return true; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_378() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_525()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3_3() { + if (jj_3R_62()) return true; + return false; + } + + private boolean jj_3_74() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3R_493() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_618()) { + jj_scanpos = xsp; + if (jj_3R_619()) return true; + } + return false; + } + + private boolean jj_3_73() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_162() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_378()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + xsp = jj_scanpos; + if (jj_3R_379()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_310() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_309() { + if (jj_3R_119()) return true; + return false; + } + + private boolean jj_3_72() { + if (jj_3R_118()) return true; + return false; + } + + private boolean jj_3R_308() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3_71() { + if (jj_3R_117()) return true; + return false; + } + + private boolean jj_3R_121() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_308()) { + jj_scanpos = xsp; + if (jj_3R_309()) { + jj_scanpos = xsp; + if (jj_3R_310()) return true; + } + } + return false; + } + + private boolean jj_3R_524() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_307() { + if (jj_3R_118()) return true; + return false; + } + + private boolean jj_3R_377() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_306() { + if (jj_scan_token(THIS)) return true; + return false; + } + + private boolean jj_3R_482() { + if (jj_scan_token(RANGE)) return true; + return false; + } + + private boolean jj_3_1() { + if (jj_scan_token(246)) return true; + if (jj_3R_61()) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_305() { + if (jj_3R_117()) return true; + return false; + } + + private boolean jj_3R_376() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_524()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3_2() { + if (jj_3R_61()) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_488() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_120() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_305()) { + jj_scanpos = xsp; + if (jj_3R_306()) { + jj_scanpos = xsp; + if (jj_3R_307()) return true; + } + } + return false; + } + + private boolean jj_3R_317() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_488()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_161() { + if (jj_scan_token(LT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_376()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + xsp = jj_scanpos; + if (jj_3R_377()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_114() { + Token xsp; + xsp = jj_scanpos; + if (jj_3_1()) { + jj_scanpos = xsp; + if (jj_3_2()) return true; + } + return false; + } + + private boolean jj_3R_312() { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(200)) { + jj_scanpos = xsp; + if (jj_3R_482()) return true; + } + return false; + } + + private boolean jj_3R_125() { + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_317()) jj_scanpos = xsp; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_303() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_478()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_523() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_375() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_374() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_523()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_117() { + if (jj_3R_111()) return true; + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_303()) jj_scanpos = xsp; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_160() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_374()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + if (jj_scan_token(GT)) return true; + xsp = jj_scanpos; + if (jj_3R_375()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_119() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_686() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_522() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_372() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_522()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_159() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_372()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_122() { + if (jj_3R_311()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_312()) jj_scanpos = xsp; + if (jj_3R_311()) return true; + return false; + } + + private boolean jj_3R_462() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_461()) return true; + return false; + } + + private boolean jj_3R_316() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_315()) return true; + return false; + } + + private boolean jj_3R_889() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_124() { + if (jj_3R_315()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_316()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_70() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3_69() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_887() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_889()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_481() { + if (jj_scan_token(INTEGER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_883() { + if (jj_scan_token(LT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_887()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3_68() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_480() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3_67() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3_66() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_311() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_480()) { + jj_scanpos = xsp; + if (jj_3R_481()) return true; + } + return false; + } + + private boolean jj_3R_487() { + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_486() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_520() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_485() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_315() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_485()) { + jj_scanpos = xsp; + if (jj_3R_486()) { + jj_scanpos = xsp; + if (jj_3R_487()) return true; + } + } + return false; + } + + private boolean jj_3R_370() { + if (jj_scan_token(MINUS)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_520()) jj_scanpos = xsp; + if (jj_scan_token(MINUS)) return true; + return false; + } + + private boolean jj_3R_598() { + if (jj_scan_token(AS)) return true; + if (jj_3R_686()) return true; + return false; + } + + private boolean jj_3R_158() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_370()) { + jj_scanpos = xsp; + if (jj_scan_token(198)) return true; + } + if (jj_scan_token(GT)) return true; + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_814() { + if (jj_scan_token(FALSE)) return true; + return false; + } + + private boolean jj_3R_813() { + if (jj_scan_token(TRUE)) return true; + return false; + } + + private boolean jj_3R_461() { + if (jj_3R_116()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_598()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_281() { + if (jj_3R_461()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_462()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_711() { + if (jj_scan_token(OPTIONAL)) return true; + if (jj_scan_token(COLON)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_813()) { + jj_scanpos = xsp; + if (jj_3R_814()) return true; + } + return false; + } + + private boolean jj_3R_710() { + if (jj_scan_token(MAXDEPTH)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_608() { + if (jj_scan_token(FROM)) return true; + return false; + } + + private boolean jj_3R_606() { + if (jj_scan_token(SKIP2)) return true; + return false; + } + + private boolean jj_3R_607() { + if (jj_scan_token(LIMIT)) return true; + return false; + } + + private boolean jj_3R_605() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_709() { + if (jj_scan_token(WHILE)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_400()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_708() { + if (jj_scan_token(WHERE)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_400()) return true; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_707() { + if (jj_scan_token(AS)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_477() { + if (jj_scan_token(COLON)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_605()) { + jj_scanpos = xsp; + if (jj_3R_606()) { + jj_scanpos = xsp; + if (jj_3R_607()) { + jj_scanpos = xsp; + if (jj_3R_608()) return true; + } + } + } + return false; + } + + private boolean jj_3R_394() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_706() { + if (jj_scan_token(CLASSES)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_705() { + if (jj_scan_token(RID)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_476() { + if (jj_scan_token(HOOK)) return true; + return false; + } + + private boolean jj_3R_704() { + if (jj_scan_token(CLASS)) return true; + if (jj_scan_token(COLON)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_639() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_638()) return true; + return false; + } + + private boolean jj_3R_296() { + if (jj_3R_477()) return true; + return false; + } + + private boolean jj_3R_295() { + if (jj_3R_476()) return true; + return false; + } + + private boolean jj_3R_638() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_704()) { + jj_scanpos = xsp; + if (jj_3R_705()) { + jj_scanpos = xsp; + if (jj_3R_706()) { + jj_scanpos = xsp; + if (jj_3R_707()) { + jj_scanpos = xsp; + if (jj_3R_708()) { + jj_scanpos = xsp; + if (jj_3R_709()) { + jj_scanpos = xsp; + if (jj_3R_710()) { + jj_scanpos = xsp; + if (jj_3R_711()) return true; + } + } + } + } + } + } + } + return false; + } + + private boolean jj_3R_521() { + if (jj_3R_638()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_639()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_115() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_295()) { + jj_scanpos = xsp; + if (jj_3R_296()) return true; + } + return false; + } + + private boolean jj_3_133() { + if (jj_3R_162()) return true; + return false; + } + + private boolean jj_3_132() { + if (jj_3R_161()) return true; + return false; + } + + private boolean jj_3R_189() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_188() { + if (jj_3R_396()) return true; + return false; + } + + private boolean jj_3R_187() { + if (jj_3R_395()) return true; + return false; + } + + private boolean jj_3_131() { + if (jj_3R_160()) return true; + return false; + } + + private boolean jj_3R_371() { + if (jj_scan_token(LBRACE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_521()) jj_scanpos = xsp; + if (jj_scan_token(RBRACE)) return true; + return false; + } + + private boolean jj_3R_884() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_186() { + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3R_519() { + if (jj_3R_162()) return true; + return false; + } + + private boolean jj_3R_185() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_394()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_518() { + if (jj_3R_161()) return true; + return false; + } + + private boolean jj_3R_517() { + if (jj_3R_160()) return true; + return false; + } + + private boolean jj_3R_368() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_517()) { + jj_scanpos = xsp; + if (jj_3R_518()) { + jj_scanpos = xsp; + if (jj_3R_519()) return true; + } + } + return false; + } + + private boolean jj_3R_79() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(EDGE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_185()) jj_scanpos = xsp; + if (jj_scan_token(FROM)) return true; + if (jj_3R_116()) return true; + if (jj_scan_token(TO)) return true; + if (jj_3R_116()) return true; + xsp = jj_scanpos; + if (jj_3R_186()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_187()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_188()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_189()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_65() { + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3_64() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3_130() { + if (jj_3R_155()) return true; + return false; + } + + private boolean jj_3R_156() { + if (jj_scan_token(DOT)) return true; + if (jj_scan_token(LPAREN)) return true; + Token xsp; + if (jj_3R_368()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_368()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + xsp = jj_scanpos; + if (jj_3R_884()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_383() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_178() { + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3_63() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_177() { + if (jj_scan_token(RETURN)) return true; + if (jj_3R_281()) return true; + return false; + } + + private boolean jj_3R_886() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_176() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3R_74() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(VERTEX)) return true; + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3R_885() { + if (jj_3R_155()) return true; + return false; + } + + private boolean jj_3R_175() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_383()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_157() { + if (jj_scan_token(DOT)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_369()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_885()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + xsp = jj_scanpos; + if (jj_3R_886()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_888() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3R_75() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(VERTEX)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_175()) { + jj_scanpos = xsp; + if (jj_3R_176()) return true; + } + xsp = jj_scanpos; + if (jj_3R_177()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_178()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_129() { + if (jj_3R_159()) return true; + return false; + } + + private boolean jj_3R_369() { + if (jj_3R_117()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_888()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_179() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_373() { + if (jj_3R_371()) return true; + return false; + } + + private boolean jj_3_128() { + if (jj_3R_158()) return true; + return false; + } + + private boolean jj_3R_76() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(VERTEX)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_179()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_876() { + if (jj_3R_159()) return true; + return false; + } + + private boolean jj_3R_155() { + if (jj_3R_125()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_373()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_875() { + if (jj_3R_883()) return true; + return false; + } + + private boolean jj_3R_874() { + if (jj_3R_158()) return true; + return false; + } + + private boolean jj_3R_77() { + if (jj_scan_token(CREATE)) return true; + if (jj_scan_token(VERTEX)) return true; + return false; + } + + private boolean jj_3_127() { + if (jj_3R_157()) return true; + return false; + } + + private boolean jj_3R_292() { + if (jj_scan_token(CONTENT)) return true; + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3_126() { + if (jj_3R_156()) return true; + return false; + } + + private boolean jj_3R_475() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3_125() { + if (jj_3R_155()) return true; + return false; + } + + private boolean jj_3R_862() { + Token xsp; + xsp = jj_scanpos; + if (jj_3_125()) { + jj_scanpos = xsp; + if (jj_3_126()) { + jj_scanpos = xsp; + if (jj_3_127()) { + jj_scanpos = xsp; + if (jj_3R_874()) { + jj_scanpos = xsp; + if (jj_3R_875()) { + jj_scanpos = xsp; + if (jj_3R_876()) return true; + } + } + } + } + } + return false; + } + + private boolean jj_3R_603() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_858() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_802() { + if (jj_scan_token(CHARACTER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_621() { + if (jj_3R_371()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_862()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_62() { + if (jj_scan_token(SET)) return true; + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_475()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_801() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3R_800() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_799() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_474() { + if (jj_scan_token(COMMA)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_603()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_693() { + if (jj_scan_token(COMMA)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_799()) { + jj_scanpos = xsp; + if (jj_3R_800()) { + jj_scanpos = xsp; + if (jj_3R_801()) { + jj_scanpos = xsp; + if (jj_3R_802()) return true; + } + } + } + if (jj_scan_token(COLON)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_473() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_692() { + if (jj_scan_token(CHARACTER_LITERAL)) return true; + return false; + } + + private boolean jj_3R_797() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_691() { + if (jj_scan_token(STRING_LITERAL)) return true; + return false; + } + + private boolean jj_3R_690() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_689() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_112() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_604() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_689()) { + jj_scanpos = xsp; + if (jj_3R_690()) { + jj_scanpos = xsp; + if (jj_3R_691()) { + jj_scanpos = xsp; + if (jj_3R_692()) return true; + } + } + } + if (jj_scan_token(COLON)) return true; + if (jj_3R_116()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_693()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3_124() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_164() { + if (jj_scan_token(LBRACE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_604()) jj_scanpos = xsp; + if (jj_scan_token(RBRACE)) return true; + return false; + } + + private boolean jj_3_61() { + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_112()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + if (jj_scan_token(VALUES)) return true; + if (jj_scan_token(LPAREN)) return true; + if (jj_3R_116()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_473()) { jj_scanpos = xsp; break; } + } + if (jj_scan_token(RPAREN)) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_474()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_873() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3_59() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_698() { + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_108() { + Token xsp; + xsp = jj_scanpos; + if (jj_3_61()) { + jj_scanpos = xsp; + if (jj_3_62()) { + jj_scanpos = xsp; + if (jj_3R_292()) return true; + } + } + return false; + } + + private boolean jj_3R_701() { + if (jj_3R_493()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_873()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_110() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_109() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_798() { + if (jj_scan_token(DOT)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_858()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_617() { + if (jj_scan_token(UNSAFE)) return true; + return false; + } + + private boolean jj_3_58() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_857() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_812() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3_60() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_109()) { + jj_scanpos = xsp; + if (jj_3R_110()) return true; + } + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_856() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_811() { + if (jj_3R_99()) return true; + return false; + } + + private boolean jj_3R_700() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_811()) { + jj_scanpos = xsp; + if (jj_3R_812()) return true; + } + return false; + } + + private boolean jj_3R_796() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_856()) { + jj_scanpos = xsp; + if (jj_3R_857()) return true; + } + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3_57() { + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3_56() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_699() { + if (jj_scan_token(FROM)) return true; + return false; + } + + private boolean jj_3R_684() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_796()) jj_scanpos = xsp; + if (jj_3R_111()) return true; + xsp = jj_scanpos; + if (jj_3R_797()) jj_scanpos = xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_798()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_616() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_699()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_700()) { + jj_scanpos = xsp; + if (jj_3_60()) return true; + } + return false; + } + + private boolean jj_3R_683() { + if (jj_scan_token(STAR)) return true; + return false; + } + + private boolean jj_3R_613() { + if (jj_3R_113()) return true; + return false; + } + + private boolean jj_3R_615() { + if (jj_scan_token(RETURN)) return true; + if (jj_3R_281()) return true; + return false; + } + + private boolean jj_3R_612() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_698()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_614() { + if (jj_3R_108()) return true; + return false; + } + + private boolean jj_3R_611() { + if (jj_3R_107()) return true; + return false; + } + + private boolean jj_3R_587() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_683()) { + jj_scanpos = xsp; + if (jj_3R_684()) return true; + } + if (jj_scan_token(COLON)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_588() { + if (jj_3R_587()) return true; + return false; + } + + private boolean jj_3R_492() { + if (jj_scan_token(INSERT)) return true; + if (jj_scan_token(INTO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_611()) { + jj_scanpos = xsp; + if (jj_3R_612()) { + jj_scanpos = xsp; + if (jj_3R_613()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_614()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_615()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_616()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_617()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_438() { + if (jj_scan_token(FETCHPLAN)) return true; + if (jj_3R_587()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_588()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_479() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_304() { + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_479()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_777() { + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_776() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_657() { + if (jj_3R_111()) return true; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_118() { + if (jj_scan_token(LBRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_304()) jj_scanpos = xsp; + if (jj_scan_token(RBRACKET)) return true; + return false; + } + + private boolean jj_3R_775() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_665() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_776()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_777()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_395() { + if (jj_scan_token(RETRY)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_663() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_775()) jj_scanpos = xsp; + if (jj_scan_token(EQ)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_774() { + if (jj_scan_token(SLASHASSIGN)) return true; + return false; + } + + private boolean jj_3R_668() { + if (jj_scan_token(EXCEPTION)) return true; + return false; + } + + private boolean jj_3R_773() { + if (jj_scan_token(STARASSIGN)) return true; + return false; + } + + private boolean jj_3R_771() { + if (jj_scan_token(PLUSASSIGN)) return true; + return false; + } + + private boolean jj_3R_667() { + if (jj_scan_token(RETURN)) return true; + return false; + } + + private boolean jj_3R_570() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_667()) { + jj_scanpos = xsp; + if (jj_3R_668()) return true; + } + return false; + } + + private boolean jj_3R_396() { + if (jj_scan_token(WAIT)) return true; + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_772() { + if (jj_scan_token(MINUSASSIGN)) return true; + return false; + } + + private boolean jj_3R_770() { + if (jj_scan_token(EQ)) return true; + return false; + } + + private boolean jj_3R_409() { + if (jj_scan_token(COUNT)) return true; + return false; + } + + private boolean jj_3R_769() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_666() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_665()) return true; + return false; + } + + private boolean jj_3R_406() { + if (jj_scan_token(TIMEOUT)) return true; + if (jj_3R_61()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_570()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_655() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_769()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_770()) { + jj_scanpos = xsp; + if (jj_3R_771()) { + jj_scanpos = xsp; + if (jj_3R_772()) { + jj_scanpos = xsp; + if (jj_3R_773()) { + jj_scanpos = xsp; + if (jj_3R_774()) return true; + } + } + } + } + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_567() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_566() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_664() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_663()) return true; + return false; + } + + private boolean jj_3R_565() { + if (jj_scan_token(REMOVE)) return true; + if (jj_3R_665()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_666()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_662() { + if (jj_scan_token(ADD)) return true; + return false; + } + + private boolean jj_3R_795() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_661() { + if (jj_scan_token(INCREMENT)) return true; + return false; + } + + private boolean jj_3R_794() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_393() { + if (jj_scan_token(BATCH)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_566()) { + jj_scanpos = xsp; + if (jj_3R_567()) return true; + } + return false; + } + + private boolean jj_3R_660() { + if (jj_scan_token(CONTENT)) return true; + return false; + } + + private boolean jj_3R_793() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_659() { + if (jj_scan_token(MERGE)) return true; + return false; + } + + private boolean jj_3R_564() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_661()) { + jj_scanpos = xsp; + if (jj_3R_662()) return true; + } + if (jj_3R_663()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_664()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_792() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_682() { + if (jj_scan_token(OFFSET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_794()) { + jj_scanpos = xsp; + if (jj_3R_795()) return true; + } + return false; + } + + private boolean jj_3R_658() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_657()) return true; + return false; + } + + private boolean jj_3R_855() { + if (jj_scan_token(ASC)) return true; + return false; + } + + private boolean jj_3R_408() { + if (jj_scan_token(AFTER)) return true; + return false; + } + + private boolean jj_3R_563() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_659()) { + jj_scanpos = xsp; + if (jj_3R_660()) return true; + } + if (jj_3R_164()) return true; + return false; + } + + private boolean jj_3R_681() { + if (jj_scan_token(SKIP2)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_792()) { + jj_scanpos = xsp; + if (jj_3R_793()) return true; + } + return false; + } + + private boolean jj_3R_656() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_655()) return true; + return false; + } + + private boolean jj_3R_849() { + if (jj_scan_token(258)) return true; + return false; + } + + private boolean jj_3R_584() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_681()) { + jj_scanpos = xsp; + if (jj_3R_682()) return true; + } + return false; + } + + private boolean jj_3R_562() { + if (jj_scan_token(PUT)) return true; + if (jj_3R_657()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_658()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_569() { + if (jj_3R_115()) return true; + return false; + } + + private boolean jj_3R_848() { + if (jj_scan_token(257)) return true; + return false; + } + + private boolean jj_3R_767() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_848()) { + jj_scanpos = xsp; + if (jj_3R_849()) return true; + } + return false; + } + + private boolean jj_3R_568() { + if (jj_3R_61()) return true; + return false; + } + + private boolean jj_3R_561() { + if (jj_scan_token(SET)) return true; + if (jj_3R_655()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_656()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_852() { + if (jj_scan_token(ASC)) return true; + return false; + } + + private boolean jj_3R_392() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_561()) { + jj_scanpos = xsp; + if (jj_3R_562()) { + jj_scanpos = xsp; + if (jj_3R_563()) { + jj_scanpos = xsp; + if (jj_3R_564()) { + jj_scanpos = xsp; + if (jj_3R_565()) return true; + } + } + } + } + return false; + } + + private boolean jj_3R_405() { + if (jj_scan_token(LIMIT)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_568()) { + jj_scanpos = xsp; + if (jj_3R_569()) return true; + } + return false; + } + + private boolean jj_3R_98() { + if (jj_scan_token(HA)) return true; + if (jj_scan_token(SYNC)) return true; + if (jj_scan_token(CLUSTER)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_767()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_583() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_766() { + if (jj_scan_token(256)) return true; + return false; + } + + private boolean jj_3R_765() { + if (jj_scan_token(255)) return true; + return false; + } + + private boolean jj_3R_415() { + if (jj_scan_token(DEFAULT_)) return true; + return false; + } + + private boolean jj_3R_414() { + if (jj_scan_token(SHARED)) return true; + return false; + } + + private boolean jj_3R_205() { + if (jj_3R_406()) return true; + return false; + } + + private boolean jj_3R_398() { + if (jj_scan_token(AFTER)) return true; + return false; + } + + private boolean jj_3R_204() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_413() { + if (jj_scan_token(NONE)) return true; + return false; + } + + private boolean jj_3R_412() { + if (jj_scan_token(RECORD)) return true; + return false; + } + + private boolean jj_3R_97() { + if (jj_scan_token(HA)) return true; + if (jj_scan_token(SYNC)) return true; + if (jj_scan_token(DATABASE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_765()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_766()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_579() { + if (jj_scan_token(COMMA)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_435() { + if (jj_scan_token(UNWIND)) return true; + if (jj_3R_111()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_583()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_410() { + if (jj_3R_281()) return true; + return false; + } + + private boolean jj_3R_853() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_203() { + if (jj_scan_token(LOCK)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_412()) { + jj_scanpos = xsp; + if (jj_3R_413()) { + jj_scanpos = xsp; + if (jj_3R_414()) { + jj_scanpos = xsp; + if (jj_3R_415()) return true; + } + } + } + return false; + } + + private boolean jj_3R_790() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_202() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_201() { + if (jj_3R_411()) return true; + return false; + } + + private boolean jj_3R_789() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_407() { + if (jj_scan_token(BEFORE)) return true; + return false; + } + + private boolean jj_3R_854() { + if (jj_scan_token(DESC)) return true; + return false; + } + + private boolean jj_3R_791() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_854()) { + jj_scanpos = xsp; + if (jj_3R_855()) return true; + } + return false; + } + + private boolean jj_3R_96() { + if (jj_scan_token(HA)) return true; + if (jj_scan_token(REMOVE)) return true; + if (jj_scan_token(SERVER)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_847() { + if (jj_scan_token(254)) return true; + return false; + } + + private boolean jj_3R_783() { + if (jj_scan_token(ASC)) return true; + return false; + } + + private boolean jj_3R_200() { + if (jj_scan_token(RETURN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_407()) { + jj_scanpos = xsp; + if (jj_3R_408()) { + jj_scanpos = xsp; + if (jj_3R_409()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_410()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_788() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_853()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_199() { + if (jj_scan_token(UPSERT)) return true; + return false; + } + + private boolean jj_3R_433() { + if (jj_scan_token(GROUP)) return true; + if (jj_scan_token(BY)) return true; + if (jj_3R_116()) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_579()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_198() { + if (jj_3R_392()) return true; + return false; + } + + private boolean jj_3R_846() { + if (jj_scan_token(253)) return true; + return false; + } + + private boolean jj_3R_845() { + if (jj_scan_token(252)) return true; + return false; + } + + private boolean jj_3R_844() { + if (jj_scan_token(251)) return true; + return false; + } + + private boolean jj_3R_843() { + if (jj_scan_token(250)) return true; + return false; + } + + private boolean jj_3R_842() { + if (jj_scan_token(249)) return true; + return false; + } + + private boolean jj_3R_764() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_842()) { + jj_scanpos = xsp; + if (jj_3R_843()) { + jj_scanpos = xsp; + if (jj_3R_844()) { + jj_scanpos = xsp; + if (jj_3R_845()) { + jj_scanpos = xsp; + if (jj_3R_846()) { + jj_scanpos = xsp; + if (jj_3R_847()) return true; + } + } + } + } + } + return false; + } + + private boolean jj_3R_81() { + if (jj_scan_token(UPDATE)) return true; + if (jj_3R_190()) return true; + Token xsp; + if (jj_3R_198()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_198()) { jj_scanpos = xsp; break; } + } + xsp = jj_scanpos; + if (jj_3R_199()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_200()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_201()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_202()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_203()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_204()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_205()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_850() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_786() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_197() { + if (jj_3R_406()) return true; + return false; + } + + private boolean jj_3R_785() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_196() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_851() { + if (jj_scan_token(DESC)) return true; + return false; + } + + private boolean jj_3R_787() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_851()) { + jj_scanpos = xsp; + if (jj_3R_852()) return true; + } + return false; + } + + private boolean jj_3R_404() { + if (jj_scan_token(DEFAULT_)) return true; + return false; + } + + private boolean jj_3R_399() { + if (jj_3R_281()) return true; + return false; + } + + private boolean jj_3R_780() { + if (jj_scan_token(ASC)) return true; + return false; + } + + private boolean jj_3R_403() { + if (jj_scan_token(SHARED)) return true; + return false; + } + + private boolean jj_3R_680() { + if (jj_scan_token(LPAREN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_788()) { + jj_scanpos = xsp; + if (jj_3R_789()) { + jj_scanpos = xsp; + if (jj_3R_790()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_791()) jj_scanpos = xsp; + if (jj_scan_token(RPAREN)) return true; + return false; + } + + private boolean jj_3R_784() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_850()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_402() { + if (jj_scan_token(NONE)) return true; + return false; + } + + private boolean jj_3R_401() { + if (jj_scan_token(RECORD)) return true; + return false; + } + + private boolean jj_3R_95() { + if (jj_scan_token(HA)) return true; + if (jj_scan_token(STATUS)) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_764()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_397() { + if (jj_scan_token(BEFORE)) return true; + return false; + } + + private boolean jj_3R_195() { + if (jj_scan_token(LOCK)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_401()) { + jj_scanpos = xsp; + if (jj_3R_402()) { + jj_scanpos = xsp; + if (jj_3R_403()) { + jj_scanpos = xsp; + if (jj_3R_404()) return true; + } + } + } + return false; + } + + private boolean jj_3R_194() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_193() { + if (jj_scan_token(RETURN)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_397()) { + jj_scanpos = xsp; + if (jj_3R_398()) return true; + } + xsp = jj_scanpos; + if (jj_3R_399()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_679() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_784()) { + jj_scanpos = xsp; + if (jj_3R_785()) { + jj_scanpos = xsp; + if (jj_3R_786()) return true; + } + } + xsp = jj_scanpos; + if (jj_3R_787()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_192() { + if (jj_scan_token(UPSERT)) return true; + return false; + } + + private boolean jj_3R_191() { + if (jj_3R_392()) return true; + return false; + } + + private boolean jj_3R_87() { + if (jj_scan_token(DROP)) return true; + if (jj_scan_token(SEQUENCE)) return true; + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_781() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3R_747() { + if (jj_scan_token(CACHE)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_677() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_582() { + if (jj_scan_token(COMMA)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_679()) { + jj_scanpos = xsp; + if (jj_3R_680()) return true; + } + return false; + } + + private boolean jj_3R_746() { + if (jj_scan_token(INCREMENT)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_745() { + if (jj_scan_token(START)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_676() { + if (jj_3R_114()) return true; + return false; + } + + private boolean jj_3R_782() { + if (jj_scan_token(DESC)) return true; + return false; + } + + private boolean jj_3R_678() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_782()) { + jj_scanpos = xsp; + if (jj_3R_783()) return true; + } + return false; + } + + private boolean jj_3R_675() { + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_781()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_80() { + if (jj_scan_token(UPDATE)) return true; + if (jj_scan_token(EDGE)) return true; + if (jj_3R_190()) return true; + Token xsp; + if (jj_3R_191()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3R_191()) { jj_scanpos = xsp; break; } + } + xsp = jj_scanpos; + if (jj_3R_192()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_193()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_194()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_195()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_196()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_197()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_280() { + if (jj_3R_393()) return true; + return false; + } + + private boolean jj_3R_279() { + if (jj_3R_405()) return true; + return false; + } + + private boolean jj_3R_278() { + if (jj_scan_token(WHERE)) return true; + if (jj_3R_400()) return true; + return false; + } + + private boolean jj_3R_85() { + if (jj_scan_token(ALTER)) return true; + if (jj_scan_token(SEQUENCE)) return true; + if (jj_3R_111()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_745()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_746()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_747()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_277() { + if (jj_3R_111()) return true; + return false; + } + + private boolean jj_3R_742() { + if (jj_scan_token(CACHE)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_741() { + if (jj_scan_token(INCREMENT)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_740() { + if (jj_scan_token(START)) return true; + if (jj_3R_116()) return true; + return false; + } + + private boolean jj_3R_778() { + if (jj_3R_126()) return true; + return false; + } + + private boolean jj_3_55() { + if (jj_3R_106()) return true; + return false; + } + + private boolean jj_3R_673() { + if (jj_scan_token(RECORD_ATTRIBUTE)) return true; + return false; + } + + private boolean jj_3R_105() { + if (jj_scan_token(DELETE)) return true; + if (jj_scan_token(EDGE)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_277()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_278()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_279()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_280()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3_54() { + if (jj_3R_99()) return true; + return false; + } + + /** Generated Token Manager. */ + public OrientSqlTokenManager token_source; + /** Current token. */ + public Token token; + /** Next token. */ + public Token jj_nt; + private int jj_ntk; + private Token jj_scanpos, jj_lastpos; + private int jj_la; + private int jj_gen; + final private int[] jj_la1 = new int[368]; + static private int[] jj_la1_0; + static private int[] jj_la1_1; + static private int[] jj_la1_2; + static private int[] jj_la1_3; + static private int[] jj_la1_4; + static private int[] jj_la1_5; + static private int[] jj_la1_6; + static private int[] jj_la1_7; + static private int[] jj_la1_8; + static { + jj_la1_init_0(); + jj_la1_init_1(); + jj_la1_init_2(); + jj_la1_init_3(); + jj_la1_init_4(); + jj_la1_init_5(); + jj_la1_init_6(); + jj_la1_init_7(); + jj_la1_init_8(); + } + private static void jj_la1_init_0() { + jj_la1_0 = new int[] {0x9f800,0x0,0xfc400000,0x0,0x0,0x0,0x3800,0x4000,0x0,0x0,0x0,0x9f800,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0x0,0x200000,0x0,0x800000,0x0,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x400000,0x800000,0x0,0x0,0x0,0x0,0xfc400000,0x800000,0x0,0x0,0x0,0x0,0xfc400000,0x800000,0x0,0x0,0xfc400000,0x800000,0x0,0x0,0xf0000000,0x100000,0x0,0xfc400000,0x0,0x800000,0x0,0x0,0x0,0x0,0xf0000000,0x100000,0x0,0xfc400000,0x0,0x0,0x800000,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,0x20000000,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x200000,0x800,0x800,0x800,0x200800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x10000000,0x0,0x0,0x0,0x0,0xfc600000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc404000,0x0,0xfc400000,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x0,0x0,0xfc400000,0x0,0xfc400000,0x0,0xfc400000,0x0,0xfc400000,0x0,0xfc400000,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0xfc400000,0x0,0x0,0x0,0x0,0x0,0xfc400000,0xfc400000,0x0,0xfc400000,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x1800000,0xfc400000,0x0,0xfc400000,0x0,0xfc400000,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x0,0xfc400000,0xfc400000,0xfc400000,0x0,0xfc400000,0xfc400000,0x0,0x0,0x0,0x0,0xfc400000,0x0,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0x4000000,0x0,0x0,0x0,0xfc400000,0x4000000,0x0,0x0,0xfc400000,0x0,0x0,0xfc400000,0xfc400000,0x0,0x0,0xfc400000,0xfc400000,0xfc400000,0xfc400000,0x0,0x0,0x0,0xfc400000,0xfc400000,0x0,0xfc400000,0xfc400000,0x0,0x98000,0xfc400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc400000,0x9f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_1() { + jj_la1_1 = new int[] {0x4080000,0x0,0x8802183,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x4080000,0x0,0x4000000,0x0,0x400,0x2800,0x2c00,0x2c00,0x40000,0x4000,0x800000,0x400000,0x40000000,0x10000000,0x88021a3,0x4000000,0x0,0x100,0x80,0x0,0x400,0x2800,0x2c00,0x2c00,0x40000,0x4000,0x800000,0x400000,0x40000000,0x10000000,0x0,0x8802183,0x0,0x0,0x400,0x0,0x80000000,0x0,0x10000,0x0,0x10000,0x400,0x80000,0x0,0x400,0x20000000,0x0,0x80000,0x0,0x400,0x1000,0x0,0x7,0x1000,0x0,0x0,0x0,0x1000,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x400,0x1000,0x0,0x0,0x8802183,0x0,0x400,0x1000,0x0,0x0,0x8802183,0x0,0x400,0x1000,0x8802183,0x0,0x400,0x1000,0x7,0x0,0x300000,0x88021a3,0x80000,0x0,0x800000,0x400000,0x400,0x4000,0x7,0x0,0x300000,0x88021a3,0x80000,0x4000000,0x0,0x800000,0x400000,0x400,0x4000,0x0,0x0,0x1,0x4,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x8802183,0x80000,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x80000,0x0,0x8802183,0x1,0x2000000,0x1000000,0x1000,0x0,0x8802d83,0x0,0x10000,0x0,0x0,0x0,0x0,0x88021a3,0x0,0x88021a3,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x88021a3,0x0,0x8802183,0x0,0x0,0x0,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x0,0x0,0x0,0x10,0x8,0x88021a3,0x0,0x0,0x0,0x8802183,0x0,0x88021a3,0x0,0x88021a3,0x0,0x88021a3,0x0,0x8802183,0x8802183,0x0,0x0,0x8802183,0x28000,0x28000,0x0,0x8802183,0x28000,0x28000,0x8802183,0x0,0x0,0x8802183,0x28000,0x28000,0x0,0x8802183,0x28000,0x28000,0x8802183,0x0,0x0,0x0,0x0,0x0,0x2800,0x0,0x80000,0x80000,0x0,0x88021a3,0x8802183,0x0,0x0,0x0,0x0,0x0,0x8802183,0x8802183,0x0,0x8802183,0x8802183,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10000,0x0,0x10000,0x8802183,0x0,0x8802183,0x0,0x8802183,0x0,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x0,0x0,0x20000000,0x8802183,0x20000000,0x0,0x0,0x0,0x0,0x8802183,0x0,0x8802183,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x88021a3,0x0,0x0,0x88021a3,0x0,0x88021a3,0x8802183,0x8802183,0x0,0x8802183,0x88021a3,0x0,0x20000000,0x0,0x20000000,0x8802183,0x0,0x0,0x20000000,0x8802183,0x0,0x0,0x8802183,0x0,0x200,0x0,0x0,0x8802183,0x0,0x200,0x0,0x8802183,0x0,0x0,0x8802183,0x8802183,0x0,0x0,0x8802183,0x8802183,0x8802183,0x8802183,0x0,0x0,0x0,0x8802183,0x8802183,0x0,0x8802183,0x8802183,0x0,0x0,0x8802183,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2000000,0x88021a3,0x4080000,0x0,0x0,0x4,0x8000000,0x0,0x4,0x8000000,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_2() { + jj_la1_2 = new int[] {0x10a2000,0x0,0xfffbff9c,0x0,0x0,0x0,0x80000,0x0,0x2000,0x0,0x0,0x10a2000,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x40,0x0,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x1c,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0xfffbff9c,0x1c,0xfffbff9c,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x1000,0x0,0xfffbff9c,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0xfffbff9c,0x0,0xfffbff9c,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x540,0x0,0x540,0xfffbff9c,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x18000,0x40000,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0xfffbff9c,0x0,0x0,0x0,0x200000,0x0,0x0,0x400000,0x800000,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0xfffbff9c,0xfffbff9c,0xfffbff9c,0x0,0xfffbff9c,0xfffbff9c,0xfe800a00,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0x0,0x0,0xfffbff9c,0xfffbff9c,0x0,0x0,0xfffbff9c,0xfffbff9c,0xfffbff9c,0xfffbff9c,0x0,0x0,0x0,0xfffbff9c,0xfffbff9c,0x0,0xfffbff9c,0xfffbff9c,0x0,0x0,0xfffbff9c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffbff9c,0x10a2000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_3() { + jj_la1_3 = new int[] {0xf00e2408,0x80000000,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x400,0xf00c2000,0xf00e2408,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x800000,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x0,0xffffffff,0x0,0xffffffff,0x0,0xffffffff,0x0,0xffffffff,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0xffffffff,0x0,0x0,0x0,0x0,0x0,0xffffffff,0xffffffff,0x0,0xffffffff,0xffffffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0xffffffff,0x0,0xffffffff,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0xffffffff,0x0,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0xffffffff,0x0,0x0,0xffffffff,0x0,0xffffffff,0xffffffff,0xffffffff,0x0,0xffffffff,0xffffffff,0x7,0x0,0x80000000,0x0,0xffffffff,0x0,0x0,0x0,0xffffffff,0x80000000,0x20,0xffffffff,0x0,0x0,0x100,0x0,0xffffffff,0x0,0x0,0x100,0xffffffff,0x0,0x40,0xffffffff,0xffffffff,0x0,0x40,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0x0,0x800,0x0,0xffffffff,0xffffffff,0x0,0xffffffff,0xffffffff,0x10000,0xf00000,0xffffffff,0x0,0x0,0x0,0x2000000,0x0,0x4000000,0x8000000,0x0,0x0,0xffffffff,0xf00e2408,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_4() { + jj_la1_4 = new int[] {0x20406,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x20406,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x200fffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0xfffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x20000000,0x0,0x3ffff,0x0,0x20000000,0x2003ffff,0x0,0x20000000,0x2003ffff,0x0,0x0,0x0,0x0,0x0,0x20000000,0x2003ffff,0x0,0x0,0x0,0x0,0x20000000,0x2003ffff,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x200fffff,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x0,0x200,0x200fffff,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x200fffff,0x0,0x200fffff,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200fffff,0x0,0x200fffff,0x0,0x0,0x20000000,0xfffff,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200fffff,0x0,0x0,0x0,0x3ffff,0x0,0x200fffff,0x0,0x200fffff,0x0,0x200fffff,0x0,0x200fffff,0x200fffff,0x0,0x0,0x200bffff,0x0,0x0,0x0,0x200bffff,0x0,0x0,0x200bffff,0x0,0x0,0x200bffff,0x0,0x0,0x0,0x200bffff,0x0,0x0,0x200bffff,0x0,0x0,0x20000000,0x20000000,0x20000000,0x0,0x20000000,0x0,0x0,0x0,0x200fffff,0x3ffff,0x20000000,0x0,0x0,0x0,0x0,0x3ffff,0xbffff,0x0,0xbffff,0xbffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8100,0x0,0x8100,0x3ffff,0x0,0x3ffff,0x0,0x3ffff,0x0,0x3ffff,0x0,0x0,0x3ffff,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x2003ffff,0x0,0x0,0x20000000,0x20000000,0x20000000,0x3ffff,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x3ffff,0x0,0x3ffff,0x2003ffff,0x2003ffff,0x0,0x3ffff,0x3ffff,0x0,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x3ffff,0x0,0x0,0xbffff,0x0,0x0,0x0,0x0,0xbffff,0x0,0x0,0x0,0x3ffff,0x0,0x0,0x3ffff,0x3ffff,0x0,0x0,0x3ffff,0x3ffff,0x3ffff,0x3ffff,0x8,0x0,0x0,0x2003ffff,0x3ffff,0x0,0xbffff,0xbffff,0x0,0x0,0x3ffff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x200fffff,0x20406,0x0,0x80,0x0,0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_5() { + jj_la1_5 = new int[] {0x20000,0x20000,0x0,0x0,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000aec2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x8000,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x8000,0x0,0x0,0x40000,0x0,0x60008800,0x40000,0x0,0x60008800,0x0,0x0,0x0,0x0,0x40000,0x0,0x60008800,0x0,0x0,0x0,0x40000,0x0,0x60008800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000aec2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000aec2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x40000,0x0,0x0,0x40000,0x40000,0x0,0x88000,0x800000,0x88000,0x88000,0x800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x40000,0x40000,0x40000,0x40000,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x60000000,0x0,0x40000,0x0,0x40000,0x0,0x0,0x40000,0x6000aec2,0x40000,0x6000aec2,0x0,0x0,0x8000,0x80000,0x0,0x2600,0x0,0x0,0x6000aec2,0xc0,0x600080c2,0x40000,0x800,0x0,0x60008800,0x0,0x40000,0x0,0x0,0x80000,0x80000,0x0,0x0,0x0,0x0,0x6000aec2,0x600,0x87800000,0x1800000,0xc0,0x40000,0x6000aec2,0x40000,0x6000aec2,0x40000,0x6000aec2,0x87800000,0x600088c2,0x600088c2,0x600000c0,0x88000,0x0,0x0,0x0,0x88000,0x0,0x0,0x0,0x800,0x40000,0x88000,0x0,0x0,0x0,0x88000,0x0,0x0,0x0,0x800,0x40000,0x40000,0x60000000,0x60000000,0x60000000,0x0,0x60000000,0x0,0x0,0x40000,0x6000aec2,0x8000,0x0,0x8000,0x0,0x80000,0x0,0x8000,0xc0,0x40000,0xc0,0xc0,0x2080000,0x2000000,0x2000,0x2000,0x2000,0x2000000,0x2000,0x40000,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2000,0x0,0x0,0x2000,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x8000,0x800,0x0,0x40000,0x0,0x8000,0x0,0x40000,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x800000,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x80000,0x40000,0x0,0x600,0x0,0x0,0x0,0x0,0x6000aec2,0x20000,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_6() { + jj_la1_6 = new int[] {0x0,0x0,0x40000000,0x100,0x100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x100,0x0,0x40000000,0x0,0x100,0x40000100,0x0,0x100,0x40000100,0x0,0x0,0x0,0x0,0x0,0x100,0x40000100,0x0,0x0,0x0,0x0,0x100,0x40000100,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x0,0x0,0x0,0x40000300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x0,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x10000100,0x10000100,0x0,0x40000300,0x0,0x40000300,0x0,0x200,0x0,0x0,0x0,0x0,0x180,0x4600,0x40000300,0x0,0x40000300,0x0,0x0,0x100,0x40000200,0x0,0x0,0x40000000,0x0,0x100,0x100,0x0,0x0,0x0,0x0,0x60000300,0x0,0x80000017,0x0,0x40000000,0x0,0x40000300,0x0,0x40000300,0x0,0x40000300,0x80000017,0x40000300,0x40000300,0x0,0x0,0x40000100,0x0,0x0,0x0,0x40000100,0x0,0x0,0x40000100,0x0,0x0,0x40000100,0x0,0x0,0x0,0x40000100,0x0,0x0,0x40000100,0x0,0x0,0x100,0x100,0x100,0x0,0x100,0x0,0x0,0x0,0x40000300,0x40000200,0x300,0x0,0x200,0x0,0x200,0x40000200,0x40000000,0x0,0x40000000,0x40000000,0x140,0x0,0x0,0x0,0x0,0x140,0x0,0x0,0x0,0x0,0x0,0x40000000,0x140,0x40000000,0x140,0x40000000,0x140,0x40000000,0x140,0x0,0x40000000,0x140,0x0,0x40000000,0x140,0x0,0x0,0x0,0x0,0x40000100,0x0,0x0,0x100,0x100,0x100,0x40000000,0x0,0x40000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,0x180,0x180,0x40000000,0x0,0x40000000,0x40000100,0x40000100,0x0,0x40000000,0x40000000,0x0,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x40000000,0x0,0x0,0x40000000,0x0,0x0,0x0,0x0,0x40000000,0x0,0x0,0x0,0x40000000,0x0,0x0,0x40000000,0x40000000,0x0,0x0,0x40000000,0x40000000,0x40000200,0x40000200,0x0,0x0,0x200,0x40000100,0x40000000,0x100,0x40000000,0x40000000,0x0,0x0,0x40000200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + private static void jj_la1_init_7() { + jj_la1_7 = new int[] {0x0,0x0,0x1900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60000,0x0,0x0,0x0,0x400000,0x400000,0x0,0x1900,0x0,0x400000,0x401900,0x0,0x400000,0x401900,0x0,0x0,0x0,0x0,0x0,0x400000,0x401900,0x0,0x0,0x0,0x0,0x400000,0x401900,0x0,0x0,0x0,0x1900,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400,0x61900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400,0x400,0x0,0x400,0x1900,0x0,0x0,0x0,0x0,0x0,0x1900,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x1900,0x0,0x0,0x460400,0x81900,0x60000,0x0,0x1900,0x800000,0x0,0x0,0x1c000,0x1e000,0x0,0x0,0x401900,0x0,0x10,0x0,0x1900,0x0,0x401900,0x0,0x401900,0x0,0x401900,0x12,0x1900,0x1900,0x0,0x0,0x401900,0x0,0x0,0x0,0x401900,0x0,0x0,0x401900,0x0,0x0,0x401900,0x0,0x0,0x0,0x401900,0x0,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x1900,0x0,0x0,0x0,0x0,0x0,0x1900,0x1900,0x0,0x1900,0x1900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1900,0x0,0x1900,0x0,0x1900,0x0,0x1900,0x0,0x0,0x1900,0x0,0x0,0x1900,0x0,0x0,0x0,0x0,0x0,0x1900,0x0,0x0,0x400000,0x400000,0x400000,0x1900,0x0,0x61900,0x0,0x0,0x0,0x0,0x0,0x400,0x0,0x0,0x1900,0x0,0x0,0x1900,0x0,0x1900,0x1900,0x1900,0x0,0x1001900,0x1900,0x0,0x0,0x0,0x0,0x1900,0x0,0x0,0x0,0x1900,0x0,0x0,0x1900,0x100,0x0,0x0,0x0,0x1900,0x100,0x0,0x0,0x1900,0x0,0x0,0x1900,0x1900,0x0,0x0,0x1900,0x1900,0x801900,0x801900,0x400,0x0,0x0,0x1900,0x1900,0x0,0x1900,0x1900,0x0,0x0,0x1d00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x401900,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000000,0x7e000000,0x80000000,0x0,0x0,0x0,}; + } + private static void jj_la1_init_8() { + jj_la1_8 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x6,0x6,}; + } + final private JJCalls[] jj_2_rtns = new JJCalls[144]; + private boolean jj_rescan = false; + private int jj_gc = 0; + + /** Constructor with user supplied CharStream. */ + public OrientSql(CharStream stream) { + token_source = new OrientSqlTokenManager(stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 368; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + /** Reinitialise. */ + public void ReInit(CharStream stream) { + token_source.ReInit(stream); + token = new Token(); + jj_ntk = -1; + jjtree.reset(); + jj_gen = 0; + for (int i = 0; i < 368; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + /** Constructor with generated Token Manager. */ + public OrientSql(OrientSqlTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 368; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + /** Reinitialise. */ + public void ReInit(OrientSqlTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jjtree.reset(); + jj_gen = 0; + for (int i = 0; i < 368; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + if (++jj_gc > 100) { + jj_gc = 0; + for (int i = 0; i < jj_2_rtns.length; i++) { + JJCalls c = jj_2_rtns[i]; + while (c != null) { + if (c.gen < jj_gen) c.first = null; + c = c.next; + } + } + } + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + static private final class LookaheadSuccess extends java.lang.Error { } + final private LookaheadSuccess jj_ls = new LookaheadSuccess(); + private boolean jj_scan_token(int kind) { + if (jj_scanpos == jj_lastpos) { + jj_la--; + if (jj_scanpos.next == null) { + jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken(); + } else { + jj_lastpos = jj_scanpos = jj_scanpos.next; + } + } else { + jj_scanpos = jj_scanpos.next; + } + if (jj_rescan) { + int i = 0; Token tok = token; + while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; } + if (tok != null) jj_add_error_token(kind, i); + } + if (jj_scanpos.kind != kind) return true; + if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls; + return false; + } + + +/** Get the next Token. */ + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + +/** Get the specific Token. */ + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.List jj_expentries = new java.util.ArrayList(); + private int[] jj_expentry; + private int jj_kind = -1; + private int[] jj_lasttokens = new int[100]; + private int jj_endpos; + + private void jj_add_error_token(int kind, int pos) { + if (pos >= 100) return; + if (pos == jj_endpos + 1) { + jj_lasttokens[jj_endpos++] = kind; + } else if (jj_endpos != 0) { + jj_expentry = new int[jj_endpos]; + for (int i = 0; i < jj_endpos; i++) { + jj_expentry[i] = jj_lasttokens[i]; + } + jj_entries_loop: for (java.util.Iterator it = jj_expentries.iterator(); it.hasNext();) { + int[] oldentry = (int[])(it.next()); + if (oldentry.length == jj_expentry.length) { + for (int i = 0; i < jj_expentry.length; i++) { + if (oldentry[i] != jj_expentry[i]) { + continue jj_entries_loop; + } + } + jj_expentries.add(jj_expentry); + break jj_entries_loop; + } + } + if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind; + } + } + + /** Generate ParseException. */ + public ParseException generateParseException() { + jj_expentries.clear(); + boolean[] la1tokens = new boolean[259]; + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 368; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1< jj_gen) { + jj_la = p.arg; jj_lastpos = jj_scanpos = p.first; + switch (i) { + case 0: jj_3_1(); break; + case 1: jj_3_2(); break; + case 2: jj_3_3(); break; + case 3: jj_3_4(); break; + case 4: jj_3_5(); break; + case 5: jj_3_6(); break; + case 6: jj_3_7(); break; + case 7: jj_3_8(); break; + case 8: jj_3_9(); break; + case 9: jj_3_10(); break; + case 10: jj_3_11(); break; + case 11: jj_3_12(); break; + case 12: jj_3_13(); break; + case 13: jj_3_14(); break; + case 14: jj_3_15(); break; + case 15: jj_3_16(); break; + case 16: jj_3_17(); break; + case 17: jj_3_18(); break; + case 18: jj_3_19(); break; + case 19: jj_3_20(); break; + case 20: jj_3_21(); break; + case 21: jj_3_22(); break; + case 22: jj_3_23(); break; + case 23: jj_3_24(); break; + case 24: jj_3_25(); break; + case 25: jj_3_26(); break; + case 26: jj_3_27(); break; + case 27: jj_3_28(); break; + case 28: jj_3_29(); break; + case 29: jj_3_30(); break; + case 30: jj_3_31(); break; + case 31: jj_3_32(); break; + case 32: jj_3_33(); break; + case 33: jj_3_34(); break; + case 34: jj_3_35(); break; + case 35: jj_3_36(); break; + case 36: jj_3_37(); break; + case 37: jj_3_38(); break; + case 38: jj_3_39(); break; + case 39: jj_3_40(); break; + case 40: jj_3_41(); break; + case 41: jj_3_42(); break; + case 42: jj_3_43(); break; + case 43: jj_3_44(); break; + case 44: jj_3_45(); break; + case 45: jj_3_46(); break; + case 46: jj_3_47(); break; + case 47: jj_3_48(); break; + case 48: jj_3_49(); break; + case 49: jj_3_50(); break; + case 50: jj_3_51(); break; + case 51: jj_3_52(); break; + case 52: jj_3_53(); break; + case 53: jj_3_54(); break; + case 54: jj_3_55(); break; + case 55: jj_3_56(); break; + case 56: jj_3_57(); break; + case 57: jj_3_58(); break; + case 58: jj_3_59(); break; + case 59: jj_3_60(); break; + case 60: jj_3_61(); break; + case 61: jj_3_62(); break; + case 62: jj_3_63(); break; + case 63: jj_3_64(); break; + case 64: jj_3_65(); break; + case 65: jj_3_66(); break; + case 66: jj_3_67(); break; + case 67: jj_3_68(); break; + case 68: jj_3_69(); break; + case 69: jj_3_70(); break; + case 70: jj_3_71(); break; + case 71: jj_3_72(); break; + case 72: jj_3_73(); break; + case 73: jj_3_74(); break; + case 74: jj_3_75(); break; + case 75: jj_3_76(); break; + case 76: jj_3_77(); break; + case 77: jj_3_78(); break; + case 78: jj_3_79(); break; + case 79: jj_3_80(); break; + case 80: jj_3_81(); break; + case 81: jj_3_82(); break; + case 82: jj_3_83(); break; + case 83: jj_3_84(); break; + case 84: jj_3_85(); break; + case 85: jj_3_86(); break; + case 86: jj_3_87(); break; + case 87: jj_3_88(); break; + case 88: jj_3_89(); break; + case 89: jj_3_90(); break; + case 90: jj_3_91(); break; + case 91: jj_3_92(); break; + case 92: jj_3_93(); break; + case 93: jj_3_94(); break; + case 94: jj_3_95(); break; + case 95: jj_3_96(); break; + case 96: jj_3_97(); break; + case 97: jj_3_98(); break; + case 98: jj_3_99(); break; + case 99: jj_3_100(); break; + case 100: jj_3_101(); break; + case 101: jj_3_102(); break; + case 102: jj_3_103(); break; + case 103: jj_3_104(); break; + case 104: jj_3_105(); break; + case 105: jj_3_106(); break; + case 106: jj_3_107(); break; + case 107: jj_3_108(); break; + case 108: jj_3_109(); break; + case 109: jj_3_110(); break; + case 110: jj_3_111(); break; + case 111: jj_3_112(); break; + case 112: jj_3_113(); break; + case 113: jj_3_114(); break; + case 114: jj_3_115(); break; + case 115: jj_3_116(); break; + case 116: jj_3_117(); break; + case 117: jj_3_118(); break; + case 118: jj_3_119(); break; + case 119: jj_3_120(); break; + case 120: jj_3_121(); break; + case 121: jj_3_122(); break; + case 122: jj_3_123(); break; + case 123: jj_3_124(); break; + case 124: jj_3_125(); break; + case 125: jj_3_126(); break; + case 126: jj_3_127(); break; + case 127: jj_3_128(); break; + case 128: jj_3_129(); break; + case 129: jj_3_130(); break; + case 130: jj_3_131(); break; + case 131: jj_3_132(); break; + case 132: jj_3_133(); break; + case 133: jj_3_134(); break; + case 134: jj_3_135(); break; + case 135: jj_3_136(); break; + case 136: jj_3_137(); break; + case 137: jj_3_138(); break; + case 138: jj_3_139(); break; + case 139: jj_3_140(); break; + case 140: jj_3_141(); break; + case 141: jj_3_142(); break; + case 142: jj_3_143(); break; + case 143: jj_3_144(); break; + } + } + p = p.next; + } while (p != null); + } catch(LookaheadSuccess ls) { } + } + jj_rescan = false; + } + + private void jj_save(int index, int xla) { + JJCalls p = jj_2_rtns[index]; + while (p.gen > jj_gen) { + if (p.next == null) { p = p.next = new JJCalls(); break; } + p = p.next; + } + p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla; + } + + static final class JJCalls { + int gen; + Token first; + int arg; + JJCalls next; + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSqlConstants.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSqlConstants.java new file mode 100644 index 00000000000..76556ab88cc --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OrientSqlConstants.java @@ -0,0 +1,758 @@ +/* Generated By:JJTree&JavaCC: Do not edit this line. OrientSqlConstants.java */ +package com.orientechnologies.orient.core.sql.parser; + + +/** + * Token literal values and constants. + * Generated by org.javacc.parser.OtherFilesGen#start() + */ +public interface OrientSqlConstants { + + /** End of File. */ + int EOF = 0; + /** RegularExpression Id. */ + int FORMAL_COMMENT = 8; + /** RegularExpression Id. */ + int MULTI_LINE_COMMENT = 9; + /** RegularExpression Id. */ + int SELECT = 11; + /** RegularExpression Id. */ + int TRAVERSE = 12; + /** RegularExpression Id. */ + int MATCH = 13; + /** RegularExpression Id. */ + int INSERT = 14; + /** RegularExpression Id. */ + int CREATE = 15; + /** RegularExpression Id. */ + int DELETE = 16; + /** RegularExpression Id. */ + int VERTEX = 17; + /** RegularExpression Id. */ + int EDGE = 18; + /** RegularExpression Id. */ + int UPDATE = 19; + /** RegularExpression Id. */ + int UPSERT = 20; + /** RegularExpression Id. */ + int FROM = 21; + /** RegularExpression Id. */ + int TO = 22; + /** RegularExpression Id. */ + int WHERE = 23; + /** RegularExpression Id. */ + int WHILE = 24; + /** RegularExpression Id. */ + int INTO = 25; + /** RegularExpression Id. */ + int VALUE = 26; + /** RegularExpression Id. */ + int VALUES = 27; + /** RegularExpression Id. */ + int SET = 28; + /** RegularExpression Id. */ + int ADD = 29; + /** RegularExpression Id. */ + int PUT = 30; + /** RegularExpression Id. */ + int MERGE = 31; + /** RegularExpression Id. */ + int CONTENT = 32; + /** RegularExpression Id. */ + int REMOVE = 33; + /** RegularExpression Id. */ + int INCREMENT = 34; + /** RegularExpression Id. */ + int AND = 35; + /** RegularExpression Id. */ + int OR = 36; + /** RegularExpression Id. */ + int NULL = 37; + /** RegularExpression Id. */ + int DEFINED = 38; + /** RegularExpression Id. */ + int ORDER = 39; + /** RegularExpression Id. */ + int GROUP = 40; + /** RegularExpression Id. */ + int BY = 41; + /** RegularExpression Id. */ + int LIMIT = 42; + /** RegularExpression Id. */ + int SKIP2 = 43; + /** RegularExpression Id. */ + int BATCH = 44; + /** RegularExpression Id. */ + int OFFSET = 45; + /** RegularExpression Id. */ + int TIMEOUT = 46; + /** RegularExpression Id. */ + int ASC = 47; + /** RegularExpression Id. */ + int AS = 48; + /** RegularExpression Id. */ + int DESC = 49; + /** RegularExpression Id. */ + int FETCHPLAN = 50; + /** RegularExpression Id. */ + int RETURN = 51; + /** RegularExpression Id. */ + int BEFORE = 52; + /** RegularExpression Id. */ + int AFTER = 53; + /** RegularExpression Id. */ + int LOCK = 54; + /** RegularExpression Id. */ + int RECORD = 55; + /** RegularExpression Id. */ + int WAIT = 56; + /** RegularExpression Id. */ + int RETRY = 57; + /** RegularExpression Id. */ + int LET = 58; + /** RegularExpression Id. */ + int CACHE = 59; + /** RegularExpression Id. */ + int NOCACHE = 60; + /** RegularExpression Id. */ + int UNSAFE = 61; + /** RegularExpression Id. */ + int PARALLEL = 62; + /** RegularExpression Id. */ + int STRATEGY = 63; + /** RegularExpression Id. */ + int DEPTH_FIRST = 64; + /** RegularExpression Id. */ + int BREADTH_FIRST = 65; + /** RegularExpression Id. */ + int LUCENE = 66; + /** RegularExpression Id. */ + int NEAR = 67; + /** RegularExpression Id. */ + int WITHIN = 68; + /** RegularExpression Id. */ + int UNWIND = 69; + /** RegularExpression Id. */ + int MAXDEPTH = 70; + /** RegularExpression Id. */ + int MINDEPTH = 71; + /** RegularExpression Id. */ + int CLASS = 72; + /** RegularExpression Id. */ + int SUPERCLASS = 73; + /** RegularExpression Id. */ + int CLASSES = 74; + /** RegularExpression Id. */ + int SUPERCLASSES = 75; + /** RegularExpression Id. */ + int EXCEPTION = 76; + /** RegularExpression Id. */ + int PROFILE = 77; + /** RegularExpression Id. */ + int STORAGE = 78; + /** RegularExpression Id. */ + int ON = 79; + /** RegularExpression Id. */ + int OFF = 80; + /** RegularExpression Id. */ + int TRUNCATE = 81; + /** RegularExpression Id. */ + int POLYMORPHIC = 82; + /** RegularExpression Id. */ + int FIND = 83; + /** RegularExpression Id. */ + int REFERENCES = 84; + /** RegularExpression Id. */ + int EXTENDS = 85; + /** RegularExpression Id. */ + int CLUSTERS = 86; + /** RegularExpression Id. */ + int ABSTRACT = 87; + /** RegularExpression Id. */ + int ALTER = 88; + /** RegularExpression Id. */ + int NAME = 89; + /** RegularExpression Id. */ + int SHORTNAME = 90; + /** RegularExpression Id. */ + int OVERSIZE = 91; + /** RegularExpression Id. */ + int STRICTMODE = 92; + /** RegularExpression Id. */ + int ADDCLUSTER = 93; + /** RegularExpression Id. */ + int REMOVECLUSTER = 94; + /** RegularExpression Id. */ + int CUSTOM = 95; + /** RegularExpression Id. */ + int CLUSTERSELECTION = 96; + /** RegularExpression Id. */ + int DESCRIPTION = 97; + /** RegularExpression Id. */ + int ENCRYPTION = 98; + /** RegularExpression Id. */ + int DROP = 99; + /** RegularExpression Id. */ + int PROPERTY = 100; + /** RegularExpression Id. */ + int FORCE = 101; + /** RegularExpression Id. */ + int METADATA = 102; + /** RegularExpression Id. */ + int INDEX = 103; + /** RegularExpression Id. */ + int COLLATE = 104; + /** RegularExpression Id. */ + int ENGINE = 105; + /** RegularExpression Id. */ + int REBUILD = 106; + /** RegularExpression Id. */ + int ID = 107; + /** RegularExpression Id. */ + int DATABASE = 108; + /** RegularExpression Id. */ + int OPTIMIZE = 109; + /** RegularExpression Id. */ + int LINK = 110; + /** RegularExpression Id. */ + int TYPE = 111; + /** RegularExpression Id. */ + int INVERSE = 112; + /** RegularExpression Id. */ + int EXPLAIN = 113; + /** RegularExpression Id. */ + int GRANT = 114; + /** RegularExpression Id. */ + int REVOKE = 115; + /** RegularExpression Id. */ + int READ = 116; + /** RegularExpression Id. */ + int EXECUTE = 117; + /** RegularExpression Id. */ + int ALL = 118; + /** RegularExpression Id. */ + int NONE = 119; + /** RegularExpression Id. */ + int FUNCTION = 120; + /** RegularExpression Id. */ + int PARAMETERS = 121; + /** RegularExpression Id. */ + int IDEMPOTENT = 122; + /** RegularExpression Id. */ + int LANGUAGE = 123; + /** RegularExpression Id. */ + int BEGIN = 124; + /** RegularExpression Id. */ + int COMMIT = 125; + /** RegularExpression Id. */ + int ROLLBACK = 126; + /** RegularExpression Id. */ + int IF = 127; + /** RegularExpression Id. */ + int ISOLATION = 128; + /** RegularExpression Id. */ + int SLEEP = 129; + /** RegularExpression Id. */ + int CONSOLE = 130; + /** RegularExpression Id. */ + int BLOB = 131; + /** RegularExpression Id. */ + int SHARED = 132; + /** RegularExpression Id. */ + int DEFAULT_ = 133; + /** RegularExpression Id. */ + int SEQUENCE = 134; + /** RegularExpression Id. */ + int START = 135; + /** RegularExpression Id. */ + int OPTIONAL = 136; + /** RegularExpression Id. */ + int COUNT = 137; + /** RegularExpression Id. */ + int HA = 138; + /** RegularExpression Id. */ + int STATUS = 139; + /** RegularExpression Id. */ + int SERVER = 140; + /** RegularExpression Id. */ + int SYNC = 141; + /** RegularExpression Id. */ + int EXISTS = 142; + /** RegularExpression Id. */ + int RID = 143; + /** RegularExpression Id. */ + int RIDS = 144; + /** RegularExpression Id. */ + int MOVE = 145; + /** RegularExpression Id. */ + int THIS = 146; + /** RegularExpression Id. */ + int RECORD_ATTRIBUTE = 147; + /** RegularExpression Id. */ + int RID_ATTR = 148; + /** RegularExpression Id. */ + int CLASS_ATTR = 149; + /** RegularExpression Id. */ + int VERSION_ATTR = 150; + /** RegularExpression Id. */ + int SIZE_ATTR = 151; + /** RegularExpression Id. */ + int TYPE_ATTR = 152; + /** RegularExpression Id. */ + int RAW_ATTR = 153; + /** RegularExpression Id. */ + int RID_ID_ATTR = 154; + /** RegularExpression Id. */ + int RID_POS_ATTR = 155; + /** RegularExpression Id. */ + int FIELDS_ATTR = 156; + /** RegularExpression Id. */ + int INTEGER_LITERAL = 157; + /** RegularExpression Id. */ + int DECIMAL_LITERAL = 158; + /** RegularExpression Id. */ + int HEX_LITERAL = 159; + /** RegularExpression Id. */ + int OCTAL_LITERAL = 160; + /** RegularExpression Id. */ + int FLOATING_POINT_LITERAL = 161; + /** RegularExpression Id. */ + int DECIMAL_FLOATING_POINT_LITERAL = 162; + /** RegularExpression Id. */ + int DECIMAL_EXPONENT = 163; + /** RegularExpression Id. */ + int HEXADECIMAL_FLOATING_POINT_LITERAL = 164; + /** RegularExpression Id. */ + int HEXADECIMAL_EXPONENT = 165; + /** RegularExpression Id. */ + int CHARACTER_LITERAL = 166; + /** RegularExpression Id. */ + int STRING_LITERAL = 167; + /** RegularExpression Id. */ + int INTEGER_RANGE = 168; + /** RegularExpression Id. */ + int TRUE = 169; + /** RegularExpression Id. */ + int FALSE = 170; + /** RegularExpression Id. */ + int LPAREN = 171; + /** RegularExpression Id. */ + int RPAREN = 172; + /** RegularExpression Id. */ + int LBRACE = 173; + /** RegularExpression Id. */ + int RBRACE = 174; + /** RegularExpression Id. */ + int LBRACKET = 175; + /** RegularExpression Id. */ + int RBRACKET = 176; + /** RegularExpression Id. */ + int SEMICOLON = 177; + /** RegularExpression Id. */ + int COMMA = 178; + /** RegularExpression Id. */ + int DOT = 179; + /** RegularExpression Id. */ + int AT = 180; + /** RegularExpression Id. */ + int DOLLAR = 181; + /** RegularExpression Id. */ + int BACKTICK = 182; + /** RegularExpression Id. */ + int EQ = 183; + /** RegularExpression Id. */ + int EQEQ = 184; + /** RegularExpression Id. */ + int LT = 185; + /** RegularExpression Id. */ + int GT = 186; + /** RegularExpression Id. */ + int BANG = 187; + /** RegularExpression Id. */ + int TILDE = 188; + /** RegularExpression Id. */ + int HOOK = 189; + /** RegularExpression Id. */ + int COLON = 190; + /** RegularExpression Id. */ + int LE = 191; + /** RegularExpression Id. */ + int GE = 192; + /** RegularExpression Id. */ + int NE = 193; + /** RegularExpression Id. */ + int NEQ = 194; + /** RegularExpression Id. */ + int SC_OR = 195; + /** RegularExpression Id. */ + int SC_AND = 196; + /** RegularExpression Id. */ + int INCR = 197; + /** RegularExpression Id. */ + int DECR = 198; + /** RegularExpression Id. */ + int PLUS = 199; + /** RegularExpression Id. */ + int MINUS = 200; + /** RegularExpression Id. */ + int STAR = 201; + /** RegularExpression Id. */ + int SLASH = 202; + /** RegularExpression Id. */ + int BIT_AND = 203; + /** RegularExpression Id. */ + int BIT_OR = 204; + /** RegularExpression Id. */ + int XOR = 205; + /** RegularExpression Id. */ + int REM = 206; + /** RegularExpression Id. */ + int LSHIFT = 207; + /** RegularExpression Id. */ + int PLUSASSIGN = 208; + /** RegularExpression Id. */ + int MINUSASSIGN = 209; + /** RegularExpression Id. */ + int STARASSIGN = 210; + /** RegularExpression Id. */ + int SLASHASSIGN = 211; + /** RegularExpression Id. */ + int ANDASSIGN = 212; + /** RegularExpression Id. */ + int ORASSIGN = 213; + /** RegularExpression Id. */ + int XORASSIGN = 214; + /** RegularExpression Id. */ + int REMASSIGN = 215; + /** RegularExpression Id. */ + int LSHIFTASSIGN = 216; + /** RegularExpression Id. */ + int RSIGNEDSHIFTASSIGN = 217; + /** RegularExpression Id. */ + int RUNSIGNEDSHIFTASSIGN = 218; + /** RegularExpression Id. */ + int ELLIPSIS = 219; + /** RegularExpression Id. */ + int RANGE = 220; + /** RegularExpression Id. */ + int NOT = 221; + /** RegularExpression Id. */ + int IN = 222; + /** RegularExpression Id. */ + int LIKE = 223; + /** RegularExpression Id. */ + int IS = 224; + /** RegularExpression Id. */ + int BETWEEN = 225; + /** RegularExpression Id. */ + int CONTAINS = 226; + /** RegularExpression Id. */ + int CONTAINSALL = 227; + /** RegularExpression Id. */ + int CONTAINSKEY = 228; + /** RegularExpression Id. */ + int CONTAINSVALUE = 229; + /** RegularExpression Id. */ + int CONTAINSTEXT = 230; + /** RegularExpression Id. */ + int MATCHES = 231; + /** RegularExpression Id. */ + int KEY = 232; + /** RegularExpression Id. */ + int INSTANCEOF = 233; + /** RegularExpression Id. */ + int CLUSTER = 234; + /** RegularExpression Id. */ + int IDENTIFIER = 235; + /** RegularExpression Id. */ + int QUOTED_IDENTIFIER = 236; + /** RegularExpression Id. */ + int INDEX_COLON = 237; + /** RegularExpression Id. */ + int INDEXVALUES_IDENTIFIER = 238; + /** RegularExpression Id. */ + int INDEXVALUESASC_IDENTIFIER = 239; + /** RegularExpression Id. */ + int INDEXVALUESDESC_IDENTIFIER = 240; + /** RegularExpression Id. */ + int CLUSTER_IDENTIFIER = 241; + /** RegularExpression Id. */ + int CLUSTER_NUMBER_IDENTIFIER = 242; + /** RegularExpression Id. */ + int METADATA_IDENTIFIER = 243; + /** RegularExpression Id. */ + int LETTER = 244; + /** RegularExpression Id. */ + int PART_LETTER = 245; + + /** Lexical state. */ + int DEFAULT = 0; + /** Lexical state. */ + int IN_FORMAL_COMMENT = 1; + /** Lexical state. */ + int IN_MULTI_LINE_COMMENT = 2; + + /** Literal token values. */ + String[] tokenImage = { + "", + "\" \"", + "\"\\t\"", + "\"\\n\"", + "\"\\r\"", + "\"\\f\"", + "", + "\"/*\"", + "\"*/\"", + "\"*/\"", + "", + "